Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,30 @@ once (below).
The plugin can't pass tool args to `agy`, so the ratio rides as a prompt
instruction and the reasoning model fills `generate_image`'s `AspectRatio`
parameter (verified live: `16:9` → 1376×768).
- **Count**: after the (optional) ratio — `x3` / `3x`, or naturally: `3
изображения …` / `3 варианта …` / `3 images …`. A plain leading number that
describes the subject («3 котика идут гулять») stays ONE image of three
kittens — a count needs the image-noun form or the `x` prefix. Images are
generated sequentially (one `agy` call each) and delivered as separate
photos; caption and buttons ride the first one. Capped at 4 per command
(each image spends a Google image-quota unit; override with the
`ANTIGRAVITY_IMAGE_MAX_COUNT` env var, hard max 10).
- **Photo reference**: attach a photo to the command message (as a compressed
photo, not a file) — `/antigravity_image кот держит вот этот товар` + фото
товара. The plugin captures the downloaded attachment via a
`message:received` hook, hands it to `agy` via `--add-dir`, and instructs the
model to reproduce that exact object (verified live: shape, colours and label
text carry over). An image sent as a FILE (document) does not dispatch the
command at all — Telegram/OpenClaw route document captions to the LLM agent.
- **Under every photo**: two inline buttons. **🔁 Ещё раз** regenerates with the
same prompt + ratio and posts a new photo; **✏️ Изменить** sends the full
command as tap-to-copy monospace so you can tweak and resend. The caption
itself also carries the tap-to-copy command (kept ≤1024 chars so the buttons
stay attached to the photo). Prompts are stored by short id in
`~/.gemini/antigravity-image-prompts.json` (callback_data is capped at 64
bytes; newest 100 kept), so buttons on very old photos may expire.
same prompt + ratio + count + photo reference and posts new photo(s);
**✏️ Изменить** sends the full command as tap-to-copy monospace so you can
tweak and resend. The caption itself also carries the tap-to-copy command
(kept ≤1024 chars so the buttons stay attached to the photo). Prompts are
stored by short id in `~/.gemini/antigravity-image-prompts.json`
(callback_data is capped at 64 bytes; newest 100 kept), so buttons on very
old photos may expire. If OpenClaw has already pruned the referenced inbound
photo, a regenerate says so and falls back to text-only.
- `/antigravity ping` — quick auth probe. `/antigravity reset` — clear default model.

Navigation is edit-in-place: tapping a button rewrites the same panel message
Expand Down
31 changes: 24 additions & 7 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,31 @@ LLM-агента*. Навигация по меню, смена модели и
аргументы тула, поэтому формат едет инструкцией в тексте промпта, а
reasoning-модель заполняет параметр `AspectRatio` тула `generate_image`
(проверено вживую: `16:9` → 1376×768).
- **Количество**: после (необязательного) формата — `x3` / `3x`, либо
естественно: `3 изображения …` / `3 варианта …`. Просто число, описывающее
сюжет («3 котика идут гулять»), остаётся ОДНОЙ картинкой с тремя котиками —
для количества нужно «картиночное» слово или префикс `x`. Картинки
генерируются последовательно (отдельный вызов `agy` на каждую) и приходят
отдельными фото; подпись и кнопки — на первом. Потолок — 4 на команду
(каждая картинка тратит единицу квоты Google; меняется env-переменной
`ANTIGRAVITY_IMAGE_MAX_COUNT`, жёсткий максимум 10).
- **Фото-референс**: приложи фото к сообщению с командой (именно фото, не
файлом) — `/antigravity_image кот держит вот этот товар` + фото товара.
Плагин ловит скачанное вложение через хук `message:received`, отдаёт его
`agy` через `--add-dir` и велит воспроизвести именно этот объект (проверено
вживую: форма, цвета и текст этикетки переносятся). Картинка, отправленная
ФАЙЛОМ (документом), команду не запускает вовсе — подписи документов
Telegram/OpenClaw уводят LLM-агенту.
- **Под каждым фото**: две inline-кнопки. **🔁 Ещё раз** — повторная генерация с
тем же промптом и форматом, новым сообщением; **✏️ Изменить** — присылает
полную команду моноширинным tap-to-copy, чтобы поправить и отправить заново.
Подпись фото тоже содержит tap-to-copy команду (держится ≤1024 символов, чтобы
кнопки не отклеились от фото). Промпты хранятся по короткому id в
`~/.gemini/antigravity-image-prompts.json` (callback_data ограничен 64
байтами; хранятся 100 последних), поэтому кнопки под очень старыми фото могут
«устареть».
тем же промптом, форматом, количеством и фото-референсом, новым сообщением;
**✏️ Изменить** — присылает полную команду моноширинным tap-to-copy, чтобы
поправить и отправить заново. Подпись фото тоже содержит tap-to-copy команду
(держится ≤1024 символов, чтобы кнопки не отклеились от фото). Промпты
хранятся по короткому id в `~/.gemini/antigravity-image-prompts.json`
(callback_data ограничен 64 байтами; хранятся 100 последних), поэтому кнопки
под очень старыми фото могут «устареть». Если OpenClaw уже вычистил исходное
вложение, повторная генерация честно предупредит и сработает только по
тексту.
- `/antigravity ping` — быстрая проверка авторизации. `/antigravity reset` —
сброс модели по умолчанию.

Expand Down
40 changes: 34 additions & 6 deletions docs/plugin-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,43 @@ from inside an interactive handler via
`api.runtime.channel.outbound.loadAdapter("telegram")` → `adapter.sendPayload({
cfg: api.runtime.config.current(), to: ctx.callback.chatId, accountId, threadId,
text, payload })`. `sendPayload` runs the full delivery pipeline: markdown→HTML,
photo upload from `mediaUrl`, and `presentation.blocks` buttons attached to the
photo (with the same opaque `tgcb1:` callback encoding, so taps route back to
this plugin's namespace handler). The handler's *return value* is still discarded
beyond `.handled`, and `respond.*` stays text-only — the adapter is the only
media/markdown path from a tap. Photo captions render markdown too (backticks →
tap-to-copy `code`), but only while the whole text fits Telegram's 1024-char
photo upload from `mediaUrl`/`mediaUrls` (plural = separate sendPhoto messages,
caption and keyboard on the FIRST one only). The handler's *return value* is still
discarded beyond `.handled`, and `respond.*` stays text-only — the adapter is the
only media/markdown path from a tap. Photo captions render markdown too (backticks
→ tap-to-copy `code`), but only while the whole text fits Telegram's 1024-char
caption cap; longer text becomes a follow-up message and the buttons detach from
the photo.

**Buttons on a PHOTO must ride `channelData.telegram.buttons`, not
`presentation.blocks`.** On the media delivery path the framework's
`renderPresentation` ALSO flattens every presentation block into the caption text
(a `- label` markdown list Telegram renders as `• label`), so presentation-carried
buttons show up twice: as caption bullets AND as the inline keyboard (the
text-only path doesn't flatten, which is why the panel never showed this). Raw
`channelData.telegram.buttons` (`[[{ text, callback_data }]]`, callback_data in
the same opaque `tgcb1:` form via `opaqueCallbackData`) skips the flatten while
still becoming a real inline keyboard — taps route back to this plugin's
namespace handler exactly like the edit-in-place keyboard. Verified against the
OpenClaw 2026.6.11 dist (`outbound-adapter`/`deliver`/`button-types` modules).

**An attached photo never reaches a command handler.** `PluginCommandContext`
carries text/addressing only — no media. The only plugin-visible route to an
attached image is `api.registerHook("message:received", handler, { name })`
(INTERNAL hook system: the key is `type:action`, NOT the config-hook name
`message_received`; a missing `opts.name` fails the whole plugin registration).
The event's payload sits under `event.context`: `content` (text/caption),
`from`, and `metadata.mediaPath(s)` — OpenClaw has already downloaded the file
to `.openclaw/media/inbound/` by then. The emit happens in dispatch-from-config
BEFORE the command executes, and a photo-with-caption command takes exactly that
path (Telegram's native `bot.command` shortcut matches only `msg.text`, never
captions — which also means an image sent as a FILE/document with a command
caption does NOT dispatch the command at all; it falls through to the LLM
agent). The plugin records a sighting (sender + media paths) from the hook and
the command handler claims it (`awaitReferenceFor`, sender-matched, short TTL),
then stages the file for `agy` via `--add-dir` + an explicit
use-this-reference-file instruction in the prompt.

Consequences baked into the plugin:

- The two action commands are printed **in the menu body as `code` spans**, not as
Expand Down
Loading