@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 +80 -7
- package/dist/index.js +2385 -270
- package/locales/en.json +64 -2
- package/locales/zh-CN.json +64 -2
- package/package.json +10 -3
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
|
|
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
|
-
|
|
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
|
|
90
|
-
`
|
|
91
|
-
`thread.
|
|
92
|
-
|
|
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
|
|