@ycodium-ai/plugin-im 0.2.2596 → 0.2.2658

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,17 +57,88 @@ snapshot wins), `turnCards.ts` (per-thread queue and lifecycle),
57
57
  `plainTurnText.ts` (view model to plain text), `telegramTurn.ts` (message
58
58
  rollover).
59
59
 
60
+ The agent's questions and the messages queued behind a running turn use the
61
+ optional `choiceCards` surface (`ImChoiceCard`, `src/adapter.ts`): Lark patches a
62
+ card, Telegram edits a message and its inline keyboard. A request is posted on
63
+ `thread.user-input-pending` (read with `listPendingUserInputs`), one card per
64
+ question, and answered with one `thread.user-input.respond` once every question
65
+ has an answer; a free-form answer is the next ordinary message in the group, and
66
+ `thread.request-resolved` freezes the cards of a question answered elsewhere or
67
+ closed unanswered. WeCom has no buttons: it writes the options as numbers and the
68
+ reply is parsed in `src/questionCards.ts`. A message that lands behind a running
69
+ turn gets a queued card (Steer, Withdraw, Resume sending) drawn from
70
+ `getThreadExecution` and refreshed when the turn intents or the queue change
71
+ (`src/queueCards.ts`, `src/queueFlow.ts`); the buttons dispatch
72
+ `thread.turn-intent.run` (steer, with the active turn id), `thread.turn-intent.cancel`
73
+ and `thread.turn-queue.resume`, and the card is the user's own action, so no
74
+ `intentAction` is sent. WeCom only gets a one-line notice, since steering
75
+ and withdrawing need buttons (resuming is the `/continue` command). Editing a queued message is not offered:
76
+ the host does not let a plugin dispatch `thread.turn-intent.edit`.
77
+
60
78
  A group keeps its place across restarts. Which thread a group is talking in, the
61
79
  team it is pointed at, the model and plan mode set from the group, the threads
62
- this plugin has opened, and the approval buttons still waiting in the group are
80
+ this plugin has opened, and the approval, question and queued-message buttons still
81
+ waiting in the group are
63
82
  written as plugin facts (`ctx.events.append`) and folded back by a pure function
64
83
  (`ctx.derivedState`, see `src/chatState.ts`). A saved thread that was deleted,
65
84
  archived or moved to another project is dropped on the next message.
66
85
 
67
- Chat commands: `/stop`, `/status`, `/new`, `/sessions`, `/resume`, `/model`,
86
+ Inbound attachments. An adapter reports a message's resources as `resources`
87
+ (`ImInboundResource`, `src/adapter.ts`) and implements `fetchResource`, which must stop reading
88
+ at `maxBytes` (`readBodyLimited`, `src/attachments.ts`) and throw `ImResourceTooLargeError`.
89
+ An adapter whose platform caps bot downloads tighter than the host sets `downloadLimitBytes`
90
+ (Telegram: 20 MB); a size the platform reports up front is checked before any download. The
91
+ core (`collectResources` in `src/index.ts`) cleans the name (`sanitizeAttachmentName`: the host
92
+ splices it into the `[Attached … "<name>" …]` line the agent reads, so newlines and double
93
+ quotes must not survive), takes the type from the bytes (`resolveMime`: platforms self-report,
94
+ Lark says PNG for every image), checks the host's limits (10 MiB image, 25 MiB other, 20 per
95
+ turn, mirrored from the host because a plugin cannot import it) and stores each one with
96
+ `ctx.attachments.store`; an image the host can inline reaches the agent as an image, anything
97
+ else as a path. A failed one is reported in the chat and left as a line in the turn text.
98
+ A message with attachments and no text starts no turn: the thread is created if the group has
99
+ none (attachments are stored per thread), the attachments wait in memory for that thread, and
100
+ the next message with text carries them. Waiting attachments are dropped when the group's
101
+ thread changes and are not persisted. Lark does not require a mention for a message that is only
102
+ a resource (`RESOURCE_MESSAGE_TYPES` in `src/larkInbound.ts`); the sender allow-list still
103
+ applies. Lark rich text is read by `src/larkPost.ts` (every image, `<image N>` placeholders in
104
+ order, the client's `md` field preferred). Known types we do not handle are reported with
105
+ `unsupportedType` and answered with one line, rate-limited per group. A group pointed at a team
106
+ cannot carry attachments. WeCom reports `image`, `file`, `video` and the images of `mixed` (a group "mention plus picture"; text items and `<image N>` placeholders keep their order, and a mixed with only images still has content, so it starts a turn) in `resources`, with the download URL and `aeskey` packed into the resource key. `fetchResource` (`src/wecomMedia.ts`) GETs the URL (valid five minutes) and decrypts: `aeskey` is base64 for a 32-byte key, the IV is its first 16 bytes, AES-256-CBC with auto padding off, then PKCS#7 removed with a **32-byte block** (pad value 1 to 32, every pad byte equal, else the media is rejected). Without an `aeskey` the bytes are used as they are. The ciphertext is capped at limit + 32, the plaintext at the limit, and the file name comes from `Content-Disposition` (`filename*=UTF-8''…` first, then `filename="…"`) through `ImFetchedResource`. A WeCom `voice` message is its transcript as text, not an attachment. `quote` is not read on any platform.
107
+
108
+ Outbound files. The plugin contributes one MCP tool, `im_send_file` (`src/sendFile.ts`;
109
+ `ctx.toolkit` name `im`), with `path` and an optional `description` (the caption). It sends only
110
+ to the group whose thread is the calling conversation (`chatOf(...).threadId`), never by
111
+ project, and fails with `not-in-chat` otherwise. The bytes come from `invocation.workspace`
112
+ (`workspace.read`: realpath-bounded to the thread's folders, regular files, 50 MiB cap) and must
113
+ be read before the handler returns, because every method rejects `invocation-ended` afterwards.
114
+ Order: `stat` (size against the adapter's `outboundLimits`, before reading), `readFile` with
115
+ `maxBytes` = the file limit, type from the bytes (`resolveMime`), then the caption line, then
116
+ `sendImage` (a picture within the image limit) or the optional adapter method `sendFile`. Audio
117
+ and video go as files. Lark uploads `file_type=stream` to `im/v1/files`; Telegram uses
118
+ `sendDocument` and falls back to it when `sendPhoto` answers 400. Failures return `isError` with
119
+ `errorCode` (`not-in-chat`, `no-workspace`, `outside-workspace`, `not-a-file`, `not-found`,
120
+ `empty`, `too-large`, `unsupported-platform`, `upload-failed`); the text for the agent is plain
121
+ English, only the caption goes through the catalog. The limits are Lark 10 MB image / 30 MB file
122
+ (Lark docs), WeCom 10 MB image (PNG, JPEG, GIF) / 20 MB file / at least 5 bytes (WeCom docs), and
123
+ Telegram 10 MB / 50 MB (from memory of the Bot API docs, not checked in this repository).
124
+
125
+ WeCom uploads over the existing long connection (`src/wecomUpload.ts`, `request` in
126
+ `src/wecomApi.ts`): `aibot_upload_media_init` (type, filename, total_size, total_chunks, md5) gives
127
+ an `upload_id`; `aibot_upload_media_chunk` sends each chunk of at most 512 KB as base64 (all chunks
128
+ in parallel up to 4, three at a time up to 10, else two; two retries per chunk after 500 ms and
129
+ 1000 ms); `aibot_upload_media_finish` gives the `media_id`; then `aibot_send_msg` carries
130
+ `{ chatid, chat_type, msgtype: "image" | "file", <msgtype>: { media_id } }`. Every request waits for
131
+ its ack by `req_id` (15 s); an `errcode` other than 0 or a missing ack fails the upload, and nothing
132
+ is sent to the chat. `chat_type` (1 single, 2 group) comes from the last inbound message of that
133
+ conversation and is left out when none has arrived since the plugin started, as plain text does.
134
+ A picture goes as `image` only when it is PNG, JPEG or GIF within 10 MB (`imageMimeTypes`).
135
+
136
+ Chat commands: `/stop`, `/continue`, `/status`, `/new`, `/sessions`, `/resume`, `/model`,
68
137
  `/plan`, `/team`, `/thread`, `/help`, `/panel`. `/sessions` and `/resume` only list and
69
138
  switch threads this plugin opened, since the host lets a plugin send only into
70
- its own threads.
139
+ its own threads. `/continue` dispatches `thread.turn-queue.resume` for the group's thread
140
+ when its queue is paused (a `/stop` or a failure pauses it), which is how WeCom, with
141
+ no buttons, resumes; it is not `/resume`, which already switches threads.
71
142
 
72
143
  On Lark and Telegram, `/panel` sends a command panel (`/help` stays text and ends with a pointer to it; on WeCom, which has no panel, it does not): every command above
73
144
  is a button (a card on Lark, an inline keyboard on Telegram), and the ones that take an argument (a thread, a team, a model) open
@@ -86,10 +157,12 @@ interface language instead. The copy lives in `locales/en.json` and
86
157
 
87
158
  ## Requirements
88
159
 
89
- A Ycodium server. The plugin asks to dispatch `thread.create`,
90
- `thread.meta.update`, `thread.turn.start`, `thread.turn.interrupt`, and
91
- `thread.approval.respond`. Those commands only land on threads this plugin
92
- started.
160
+ A Ycodium server. The plugin asks to read files in the project of the conversation that
161
+ calls its tool (`workspace.read`, used only by `im_send_file`) and to dispatch `thread.create`,
162
+ `thread.meta.update`, `thread.turn.start`, `thread.turn.interrupt`,
163
+ `thread.approval.respond`, `thread.user-input.respond`, `thread.turn-intent.run`,
164
+ `thread.turn-intent.cancel` and `thread.turn-queue.resume`. Those commands only land
165
+ on threads this plugin started.
93
166
 
94
167
  ## Install
95
168