switchroom 0.16.47 → 0.17.0

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.
Files changed (106) hide show
  1. package/dist/agent-scheduler/index.js +3 -1
  2. package/dist/auth-broker/index.js +24 -8
  3. package/dist/cli/drive-write-pretool.mjs +0 -5
  4. package/dist/cli/notion-write-pretool.mjs +3 -1
  5. package/dist/cli/switchroom.js +1358 -1030
  6. package/dist/cli/ui/index.html +84 -12
  7. package/dist/host-control/main.js +53 -17
  8. package/dist/vault/approvals/kernel-server.js +4 -1
  9. package/dist/vault/broker/server.js +201 -56
  10. package/package.json +3 -3
  11. package/profiles/_base/cron-session.sh.hbs +1 -1
  12. package/profiles/_base/start.sh.hbs +54 -3
  13. package/skills/switchroom-architecture/telegram.md +8 -15
  14. package/skills/switchroom-cli/SKILL.md +4 -5
  15. package/skills/telegram-test-harness/SKILL.md +1 -1
  16. package/telegram-plugin/README.md +18 -29
  17. package/telegram-plugin/bridge/bridge.ts +1 -41
  18. package/telegram-plugin/bridge/tool-filter.ts +3 -4
  19. package/telegram-plugin/dist/bridge/bridge.js +8 -43
  20. package/telegram-plugin/dist/gateway/gateway.js +682 -773
  21. package/telegram-plugin/dist/server.js +8 -43
  22. package/telegram-plugin/gateway/busy-key-reaper.ts +113 -0
  23. package/telegram-plugin/gateway/disconnect-flush.ts +11 -0
  24. package/telegram-plugin/gateway/escalation-bridge-gate.ts +46 -0
  25. package/telegram-plugin/gateway/gate-parity-probe.ts +102 -0
  26. package/telegram-plugin/gateway/gateway.ts +518 -624
  27. package/telegram-plugin/gateway/inbound-delivery-confirm.ts +89 -7
  28. package/telegram-plugin/gateway/inbound-spool.ts +108 -10
  29. package/telegram-plugin/gateway/model-command.ts +51 -3
  30. package/telegram-plugin/gateway/pending-inbound-buffer.ts +26 -0
  31. package/telegram-plugin/gateway/represent-guard.ts +28 -11
  32. package/telegram-plugin/gateway/status-pin-store.ts +124 -45
  33. package/telegram-plugin/gateway/worker-feed-dispatch.ts +19 -0
  34. package/telegram-plugin/history.ts +5 -0
  35. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +1 -2
  36. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +9 -1
  37. package/telegram-plugin/registry/subagents-schema.ts +126 -1
  38. package/telegram-plugin/registry/turns-schema.ts +65 -1
  39. package/telegram-plugin/session-tail.ts +26 -4
  40. package/telegram-plugin/slot-banner-driver.ts +42 -2
  41. package/telegram-plugin/status-query-telemetry.ts +100 -0
  42. package/telegram-plugin/stream-reply-handler.ts +15 -16
  43. package/telegram-plugin/subagent-watcher.ts +182 -30
  44. package/telegram-plugin/tests/buffer-gate-broadened.test.ts +4 -10
  45. package/telegram-plugin/tests/busy-key-reaper.test.ts +191 -0
  46. package/telegram-plugin/tests/emission-authority-facade.test.ts +11 -17
  47. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +5 -26
  48. package/telegram-plugin/tests/escalation-bridge-gate.test.ts +38 -0
  49. package/telegram-plugin/tests/gate-parity-probe.test.ts +171 -0
  50. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +13 -0
  51. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +14 -11
  52. package/telegram-plugin/tests/inbound-delivery-confirm.test.ts +146 -0
  53. package/telegram-plugin/tests/inbound-spool.test.ts +143 -0
  54. package/telegram-plugin/tests/model-command.test.ts +54 -1
  55. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +5 -11
  56. package/telegram-plugin/tests/nested-worker-visibility-harness.test.ts +329 -0
  57. package/telegram-plugin/tests/pending-inbound-buffer.test.ts +53 -0
  58. package/telegram-plugin/tests/progress-update-redact.test.ts +99 -0
  59. package/telegram-plugin/tests/registry-turns.test.ts +67 -0
  60. package/telegram-plugin/tests/represent-guard.test.ts +42 -6
  61. package/telegram-plugin/tests/resume-inbound-builder.test.ts +1 -0
  62. package/telegram-plugin/tests/session-tail.test.ts +10 -1
  63. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +246 -0
  64. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +0 -14
  65. package/telegram-plugin/tests/status-pin-store.test.ts +220 -5
  66. package/telegram-plugin/tests/status-query-telemetry.test.ts +115 -0
  67. package/telegram-plugin/tests/subagent-nested-dispatch.test.ts +209 -0
  68. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +37 -0
  69. package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +167 -0
  70. package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +46 -3
  71. package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +70 -0
  72. package/telegram-plugin/tests/tool-activity-summary.test.ts +16 -0
  73. package/telegram-plugin/tests/tool-filter.test.ts +1 -3
  74. package/telegram-plugin/tests/tool-label-pretool.test.ts +1 -4
  75. package/telegram-plugin/tests/turn-flush-safety.test.ts +222 -1
  76. package/telegram-plugin/tests/worker-activity-feed.test.ts +202 -9
  77. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +25 -0
  78. package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +295 -0
  79. package/telegram-plugin/tool-activity-summary.ts +19 -0
  80. package/telegram-plugin/turn-flush-safety.ts +16 -1
  81. package/telegram-plugin/uat/scenarios/jtbd-answer-pings.test.ts +8 -9
  82. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-visibility-dm.test.ts +1 -1
  83. package/telegram-plugin/uat/scenarios/jtbd-narration-intent-dm.test.ts +1 -1
  84. package/telegram-plugin/worker-activity-feed.ts +75 -15
  85. package/vendor/hindsight-memory/CHANGELOG.md +24 -0
  86. package/vendor/hindsight-memory/README.md +5 -0
  87. package/vendor/hindsight-memory/scripts/lib/client.py +31 -1
  88. package/vendor/hindsight-memory/scripts/lib/config.py +41 -2
  89. package/vendor/hindsight-memory/scripts/lib/content.py +4 -1
  90. package/vendor/hindsight-memory/scripts/lib/daemon.py +11 -2
  91. package/vendor/hindsight-memory/scripts/recall.py +74 -1
  92. package/vendor/hindsight-memory/scripts/retain.py +8 -1
  93. package/vendor/hindsight-memory/scripts/tests/test_config_client_casts.py +111 -0
  94. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +85 -1
  95. package/vendor/hindsight-memory/scripts/tests/test_recall_tag_filters.py +107 -0
  96. package/vendor/hindsight-memory/settings.json +4 -0
  97. package/vendor/hindsight-memory/tests/test_client.py +130 -0
  98. package/vendor/hindsight-memory/tests/test_config.py +47 -0
  99. package/vendor/hindsight-memory/tests/test_content.py +18 -0
  100. package/vendor/hindsight-memory/tests/test_hooks.py +62 -0
  101. package/telegram-plugin/gateway/error-envelope-card.ts +0 -64
  102. package/telegram-plugin/gateway/resolve-calling-subagent.ts +0 -78
  103. package/telegram-plugin/silent-reply.ts +0 -58
  104. package/telegram-plugin/tests/error-envelope-unlock-card.test.ts +0 -79
  105. package/telegram-plugin/tests/resolve-calling-subagent.test.ts +0 -269
  106. package/telegram-plugin/tests/silent-reply-guard.test.ts +0 -122
@@ -2,12 +2,11 @@
2
2
 
3
3
  Switchroom ships an enhanced `switchroom-telegram` MCP plugin that replaces the official marketplace plugin. It is the default — no configuration needed.
4
4
 
5
- ## 10 MCP tools
5
+ ## 9 MCP tools
6
6
 
7
7
  | Tool | What it does |
8
8
  |------|-------------|
9
- | `reply` | Send text, photos, or documents. Supports threading, topic routing, file attachments. |
10
- | `stream_reply` | Edit a single message in place as work progresses (~1/sec throttle). Use for long tasks to avoid chat spam. |
9
+ | `reply` | Send text, photos, or documents — the single final-answer tool. Chunks anything over Telegram's 4096-char limit. Supports threading, topic routing, file attachments. |
11
10
  | `react` | Add emoji reactions to messages (Telegram whitelist: 👍 👎 ❤️ 🔥 👀 🎉 etc). |
12
11
  | `edit_message` | Update a previously sent message. Edits are silent (no push notification). |
13
12
  | `delete_message` | Remove a bot-sent message (48h Telegram API limit). |
@@ -37,19 +36,13 @@ A local SQLite database (`telegram/history.db`) records every message. After a C
37
36
 
38
37
  History survives process restarts and session resets.
39
38
 
40
- ## stream_reply pattern
39
+ ## Progress while working
41
40
 
42
- For tasks taking more than ~5 seconds:
43
-
44
- ```
45
- 1. stream_reply(chat_id, "Reading the file...", done=false) ← creates message
46
- 2. stream_reply(chat_id, "Reading the file...\nParsing...", done=false) ← edits in place
47
- 3. stream_reply(chat_id, "Done! Here's the result: ...", done=true) ← locks
48
- ```
49
-
50
- Pass the **full current text** on each call (not a delta). The plugin throttles to ~1/sec.
51
-
52
- After `done=true`, send a separate `reply` if you want a push notification to the user's device (edits are silent).
41
+ For long tasks you do not need to narrate progress by editing a message. The
42
+ plugin renders an event-driven progress card (Plan → Run → Done with live tool
43
+ bullets, elapsed time, and status emoji) for free while the turn is in-flight.
44
+ Send the final answer once, with `reply` — it chunks anything over Telegram's
45
+ 4096-char limit. For an explicit mid-turn check-in use `progress_update`.
53
46
 
54
47
  ## Formatting
55
48
 
@@ -10,7 +10,7 @@ This skill is the reference for running `switchroom` CLI commands against existi
10
10
 
11
11
  **Four commands to know:**
12
12
  - `switchroom update` — full operator path: pulls images + applies config + recreates containers + runs doctor (since v0.7.8 / #918). What you want 95% of the time.
13
- - `switchroom apply` — config-only reconcile: refresh per-agent scaffolds and (re)write `~/.switchroom/compose/docker-compose.yml` without touching running containers. Use when you want to inspect the generated compose before bringing the fleet up yourself.
13
+ - `switchroom apply` — **host/operator-only**, for structural changes: refresh per-agent scaffolds and (re)write `~/.switchroom/compose/docker-compose.yml`. Its full per-agent scaffold **cannot run from inside an agent container** (no vault at the container HOME, no `docker compose` v2 plugin) — that's correct by construction, not a bug. Version rolls do NOT need it: drive the **hostd rollout** (`mcp__hostd__rollout`), which runs a `--compose-only` apply plus a per-agent restart-reconcile that refreshes each agent's templates automatically. Reserve a host-side `sudo switchroom apply` for compose regeneration / new-agent scaffolding.
14
14
  - `switchroom restart [agent]` — bounces a stuck or wedged agent
15
15
  - `switchroom version` — shows what's running (versions + health summary)
16
16
 
@@ -59,7 +59,7 @@ switchroom update --rebuild # source-checkout users: also git pull + npm bu
59
59
 
60
60
  `switchroom update` is the operator path. The CLI self-elevates via sudo internally for the per-agent scaffold dirs that need root — no need for `sudo HOME=… PATH=…` incantations.
61
61
 
62
- If you only need the config-reconcile half without restarting agents, `switchroom apply` writes `~/.switchroom/compose/docker-compose.yml` and refreshes per-agent scaffolds without touching running containers. The operator runs the docker bring-up themselves.
62
+ If you only need the config-reconcile half without restarting agents, `switchroom apply` (run **on the host by the operator**) writes `~/.switchroom/compose/docker-compose.yml` and refreshes per-agent scaffolds without touching running containers. The operator runs the docker bring-up themselves. Note this full apply cannot run from inside an agent container by construction — to roll the fleet to a new version from an agent, use the hostd rollout (`mcp__hostd__rollout`) instead of a full apply.
63
63
 
64
64
  From inside an agent's Telegram DM, the same flow is available as `/upgradestatus` (read-only) and `/update apply` (admin-gated).
65
65
 
@@ -265,12 +265,11 @@ If the user asks whether scheduled runs were missed during downtime: the schedul
265
265
 
266
266
  ## Telegram plugin reference — "what MCP tools", "how does reply work"
267
267
 
268
- The `switchroom-telegram` plugin is an enhanced fork of the official Telegram MCP plugin and is the default for all switchroom agents. It exposes **10 MCP tools** (all prefixed `mcp__switchroom-telegram__`):
268
+ The `switchroom-telegram` plugin is an enhanced fork of the official Telegram MCP plugin and is the default for all switchroom agents. It exposes **9 MCP tools** (all prefixed `mcp__switchroom-telegram__`):
269
269
 
270
270
  | Tool | Purpose |
271
271
  |---|---|
272
- | `reply` | Send a text/photo message, with optional `reply_to` for threaded quotes |
273
- | `stream_reply` | Incrementally stream a long reply (edits the same message as tokens arrive) |
272
+ | `reply` | Send a text/photo message the single final-answer tool; chunks anything over Telegram's 4096-char limit. Optional `reply_to` for threaded quotes |
274
273
  | `react` | Emoji reaction on an inbound or outbound message |
275
274
  | `edit_message` | Modify an earlier bot message's text |
276
275
  | `delete_message` | Remove an earlier bot message |
@@ -191,4 +191,4 @@ Before writing the test, ask:
191
191
  - `telegram-plugin/tests/fake-bot-api.test.ts` — meta-test of the fake;
192
192
  read first when adding new fake-bot capabilities
193
193
  - `telegram-plugin/tests/streaming-e2e.test.ts` — worked example of a
194
- larger end-to-end test (PTY → stream_reply → done)
194
+ larger end-to-end test (PTY → internal stream-reply handler → done)
@@ -9,10 +9,12 @@ and substantially extended for long-running, multi-agent deployments.
9
9
  The upstream plugin handles the basic send/receive wire protocol. This fork
10
10
  adds the ergonomics and reliability that an always-on agent fleet needs:
11
11
 
12
- - **Streaming replies** — `stream_reply` edits a single message in place as
13
- work progresses (~1/sec throttle), so users see live progress instead of
14
- silent gaps followed by a wall-of-text. Optional `lane` parameter lets each
15
- lane (e.g. `thinking` vs default `answer`) get its own message per chat+thread.
12
+ - **Live progress card** — an event-driven progress card (Plan Run → Done
13
+ with live tool bullets, elapsed time, and status emoji) edits a single
14
+ message in place as work progresses, so users see live progress instead of
15
+ silent gaps followed by a wall-of-text. This is rendered by the plugin for
16
+ free while the turn is in-flight — the model just sends the final answer once
17
+ via `reply`.
16
18
  - **Status reactions** — emoji lifecycle (👀 queued → 🤔 thinking → 👨‍💻 tool
17
19
  use → 🔥 streaming → 👍 done) on the user's own message gives "I'm working"
18
20
  feedback for free, plus stall watchdogs (🥱 30s idle, 😨 90s).
@@ -36,7 +38,7 @@ adds the ergonomics and reliability that an always-on agent fleet needs:
36
38
  - **Switchroom slash-commands** — `/agents`, `/restart`, `/logs`, `/memory`,
37
39
  `/grant`, `/dangerous`, `/permissions`, `/reconcile` etc., handled by the
38
40
  plugin without consuming Claude Code tokens.
39
- - **12 MCP tools** — `reply`, `stream_reply`, `react`, `edit_message`,
41
+ - **11 MCP tools** — `reply`, `react`, `edit_message`,
40
42
  `delete_message`, `forward_message`, `pin_message`, `send_typing`,
41
43
  `download_attachment`, `get_recent_messages`, `send_checklist`,
42
44
  `update_checklist` (the latter two ship native Telegram checklists,
@@ -196,40 +198,27 @@ When an inbound message is received, the plugin immediately reacts with an emoji
196
198
 
197
199
  Set to an empty string `""` to disable. Only Telegram's fixed emoji whitelist is accepted (👍 👎 ❤ 🔥 👀 🎉 etc). A typing indicator is also sent automatically.
198
200
 
199
- ### `stream_reply` tool (preferred for multi-step work)
201
+ ### Progress while working
200
202
 
201
- Sends or updates a streaming reply that edits one message in-place rather
202
- than sending many. Call repeatedly during long tasks with full snapshots of
203
- the current message; the plugin throttles edits to ~1/sec to respect
204
- Telegram's rate limit. Set `done=true` on the final call to lock the
205
- message.
206
-
207
- | Parameter | Required | Description |
208
- |-----------|----------|-------------|
209
- | `chat_id` | yes | Target chat ID |
210
- | `text` | yes | Full text snapshot (NOT a delta — pass the complete current content each call) |
211
- | `done` | no | `true` on final call. After `done=true` the stream is locked and further calls are no-ops. Default `false`. |
212
- | `format` | no | `"html"` (default), `"markdownv2"`, or `"text"` |
213
- | `lane` | no | Optional lane name. Each lane gets its own Telegram message per chat+thread. Use `lane: "thinking"` to surface reasoning progress alongside the main answer stream. Omit for the default answer lane. |
214
- | `message_thread_id` | no | Forum topic thread ID (auto-applied from the last inbound message if not specified) |
215
-
216
- Hard-stops at 4096 chars (Telegram message limit). On edit-404 (the message
217
- we're editing was deleted), the plugin sends a fresh message and continues
218
- the stream against the new id. A short `idleMs` pre-send debounce coalesces
219
- back-to-back snapshots before the first wire send, avoiding a redundant
220
- edit when several updates arrive in the same tick.
203
+ For long tasks the model does **not** need to narrate progress or drive a
204
+ streaming message itself. The plugin renders an event-driven progress card
205
+ (Plan Run Done with live tool bullets, elapsed time, and status emoji)
206
+ for free while the turn is in-flight. Send the final answer once, with
207
+ `reply` — it chunks anything over Telegram's 4096-char limit. (The retired
208
+ `stream_reply` tool was a redundant, worse alias of `reply`; the internal
209
+ progress-card streaming that drove it is preserved and is what renders the
210
+ live card.)
221
211
 
222
212
  ### Manual streaming progress via `edit_message`
223
213
 
224
- If you need finer control than `stream_reply` offers, you can drive the
225
- edit loop yourself:
214
+ If you want to drive an in-place edit loop yourself:
226
215
 
227
216
  1. Send an initial "thinking..." message with `reply` — note the returned `message_id`
228
217
  2. Call `edit_message` with updated text as work progresses (edits are silent — no push notification)
229
218
  3. Call `send_typing` between steps to keep the typing indicator alive (it expires after ~5s)
230
219
  4. When done, send a **new** `reply` so the user's device pings with a push notification
231
220
 
232
- In most cases `stream_reply` is simpler and is the recommended path.
221
+ In most cases the automatic progress card is simpler and is the recommended path.
233
222
 
234
223
  ### `send_typing` tool
235
224
 
@@ -75,7 +75,7 @@ const mcp = new Server(
75
75
  instructions: [
76
76
  'The sender reads Telegram, not this session. Anything you want them to see must go through the reply tool — your transcript output never reaches their chat.',
77
77
  '',
78
- 'Messages from Telegram arrive as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">. If the tag has an image_path attribute, Read that file — it is a photo the sender attached. If the tag has attachment_file_id, call download_attachment with that file_id to fetch the file, then Read the returned path. A single message may carry SEVERAL attachments (a forwarded album or a text+multi-image burst): when attachment_count is set (>1), also handle the numbered siblings — image_path_2, image_path_3, … (Read each) and attachment_file_id_2, attachment_file_id_3, … (download_attachment each). Process every one, not just the first. Reply with the reply tool — pass chat_id back. The reply and stream_reply tools quote-reply to the latest inbound user message by default, so you do NOT need to pass reply_to for normal responses. Pass reply_to (a message_id) only when quoting a specific earlier message, or pass quote:false to send a bare (non-quoted) message.',
78
+ 'Messages from Telegram arrive as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">. If the tag has an image_path attribute, Read that file — it is a photo the sender attached. If the tag has attachment_file_id, call download_attachment with that file_id to fetch the file, then Read the returned path. A single message may carry SEVERAL attachments (a forwarded album or a text+multi-image burst): when attachment_count is set (>1), also handle the numbered siblings — image_path_2, image_path_3, … (Read each) and attachment_file_id_2, attachment_file_id_3, … (download_attachment each). Process every one, not just the first. Reply with the reply tool — pass chat_id back. The reply tool quote-replies to the latest inbound user message by default, so you do NOT need to pass reply_to for normal responses. Pass reply_to (a message_id) only when quoting a specific earlier message, or pass quote:false to send a bare (non-quoted) message.',
79
79
  '',
80
80
  'reply accepts file paths (files: ["/abs/path.png"]) for attachments. Use react to add emoji reactions, edit_message for interim progress updates, and delete_message when you need to truly remove a message (prefer edit_message if you just want to change text — delete is for retraction). Edits don\'t trigger push notifications — when a long task completes, send a new reply so the user\'s device pings. Use send_typing to show a typing indicator during long operations. Use pin_message to pin important outputs. Use forward_message to quote/resurface earlier messages.',
81
81
  '',
@@ -134,46 +134,6 @@ const TOOL_SCHEMAS = [
134
134
  required: ['chat_id', 'text'],
135
135
  },
136
136
  },
137
- {
138
- name: 'stream_reply',
139
- description:
140
- 'Post the final answer for this turn. The plugin renders an event-driven progress card (Plan → Run → Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard cap is 32768 chars (the rich-message wire limit) — longer text is dropped by a defensive guard, so use `reply` for anything that long (it chunks). Calling with done=false is an error in this environment (the progress card already owns the mid-turn surface). inline_keyboard adds tappable buttons under the final message — see `reply` for shape and constraints.',
141
- inputSchema: {
142
- type: 'object',
143
- properties: {
144
- chat_id: { type: 'string' },
145
- text: { type: 'string', description: 'Full text snapshot. NOT a delta — pass the complete current content each call.' },
146
- done: { type: 'boolean', description: 'Must be true. Posts this text as the final answer for the turn and locks the message.' },
147
- message_thread_id: { type: 'string', description: 'Forum topic thread ID. Auto-applied from the last inbound message if not specified.' },
148
- origin_turn_id: { type: 'string', description: 'In a forum supergroup, pass back the origin_turn_id attribute from the <channel> message you are answering. It pins the reply to that message\'s topic even if another topic\'s turn started meanwhile. Omit in DMs / single-topic chats.' },
149
- format: { type: 'string', enum: ['html', 'markdownv2', 'text'], description: "Rendering mode. 'html' (default) converts markdown to Telegram HTML." },
150
- reply_to: { type: 'string', description: 'Message ID to quote-reply to. Overrides the default (latest inbound).' },
151
- quote: { type: 'boolean', description: 'Opt out of the default quote-reply behavior. Default: true. Ignored when reply_to is explicitly set.' },
152
- protect_content: { type: 'boolean', description: 'When true, Telegram prevents the message from being forwarded or saved.' },
153
- quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Requires reply_to.' },
154
- disable_notification: { type: 'boolean', description: 'When true, the INITIAL message send is silent (no device ping). Has no effect on subsequent edits — Telegram never pings on editMessageText. Default false. Use for mid-turn stream starts you do not want to ping; omit on the final answer.' },
155
- inline_keyboard: {
156
- type: 'array',
157
- description: '2D array of tappable buttons under the final message. Same shape and constraints as `reply.inline_keyboard` — each button has `text` and EXACTLY ONE of `url` or `callback_data`, plus optional `ack_text` (custom tap-toast; default "✓ received") and `single_use` (default true; set false to keep the keyboard tappable after a tap). Tap on a callback_data button is delivered to this agent as an inbound channel event with meta.button_callback_data set.',
158
- items: {
159
- type: 'array',
160
- items: {
161
- type: 'object',
162
- properties: {
163
- text: { type: 'string' },
164
- url: { type: 'string' },
165
- callback_data: { type: 'string' },
166
- ack_text: { type: 'string', description: 'Toast text shown on tap. Default "✓ received".' },
167
- single_use: { type: 'boolean', description: 'Default true. Set false to keep the keyboard tappable after this button is tapped.' },
168
- },
169
- required: ['text'],
170
- },
171
- },
172
- },
173
- },
174
- required: ['chat_id', 'text'],
175
- },
176
- },
177
137
  {
178
138
  name: 'react',
179
139
  description: 'Add an emoji reaction to a Telegram message. Telegram only accepts a fixed whitelist (👍 👎 ❤ 🔥 👀 🎉 etc) — non-whitelisted emoji will be rejected.',
@@ -30,13 +30,12 @@ export interface NamedTool {
30
30
  }
31
31
 
32
32
  /**
33
- * Hot tools pinned loaded — must never defer. The reply path
34
- * (reply/stream_reply) plus the frequently-used early-turn ops. Everything
35
- * NOT in this set defers under tool-search.
33
+ * Hot tools pinned loaded — must never defer. The reply path plus the
34
+ * frequently-used early-turn ops. Everything NOT in this set defers under
35
+ * tool-search.
36
36
  */
37
37
  export const ALWAYS_LOAD_TOOLS: ReadonlySet<string> = new Set([
38
38
  'reply',
39
- 'stream_reply',
40
39
  'get_recent_messages',
41
40
  'react',
42
41
  'edit_message',
@@ -23169,7 +23169,7 @@ function findActiveSessionFile(projectsDir) {
23169
23169
  }
23170
23170
  function parseChannelMeta(content) {
23171
23171
  const grab = (key) => {
23172
- const m = content.match(new RegExp(`${key}="([^"]+)"`));
23172
+ const m = content.match(new RegExp(`(?:^|[\\s"'])${key}="([^"]+)"`));
23173
23173
  return m ? m[1] : null;
23174
23174
  };
23175
23175
  return {
@@ -23377,7 +23377,12 @@ function projectSubagentLine(line, agentId, state) {
23377
23377
  if (ct === "tool_use") {
23378
23378
  const name = c.name ?? "";
23379
23379
  if (name === "Agent" || name === "Task") {
23380
- events.push({ kind: "sub_agent_nested_spawn", agentId });
23380
+ events.push({
23381
+ kind: "sub_agent_nested_spawn",
23382
+ agentId,
23383
+ toolUseId: c.id ?? null,
23384
+ input: c.input ?? undefined
23385
+ });
23381
23386
  } else {
23382
23387
  events.push({
23383
23388
  kind: "sub_agent_tool_use",
@@ -24495,7 +24500,6 @@ function createIpcClient(options) {
24495
24500
  // bridge/tool-filter.ts
24496
24501
  var ALWAYS_LOAD_TOOLS = new Set([
24497
24502
  "reply",
24498
- "stream_reply",
24499
24503
  "get_recent_messages",
24500
24504
  "react",
24501
24505
  "edit_message",
@@ -24647,7 +24651,7 @@ var mcp = new Server({ name: "telegram", version: "1.0.0" }, {
24647
24651
  instructions: [
24648
24652
  "The sender reads Telegram, not this session. Anything you want them to see must go through the reply tool \u2014 your transcript output never reaches their chat.",
24649
24653
  "",
24650
- 'Messages from Telegram arrive as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">. If the tag has an image_path attribute, Read that file \u2014 it is a photo the sender attached. If the tag has attachment_file_id, call download_attachment with that file_id to fetch the file, then Read the returned path. A single message may carry SEVERAL attachments (a forwarded album or a text+multi-image burst): when attachment_count is set (>1), also handle the numbered siblings \u2014 image_path_2, image_path_3, \u2026 (Read each) and attachment_file_id_2, attachment_file_id_3, \u2026 (download_attachment each). Process every one, not just the first. Reply with the reply tool \u2014 pass chat_id back. The reply and stream_reply tools quote-reply to the latest inbound user message by default, so you do NOT need to pass reply_to for normal responses. Pass reply_to (a message_id) only when quoting a specific earlier message, or pass quote:false to send a bare (non-quoted) message.',
24654
+ 'Messages from Telegram arrive as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">. If the tag has an image_path attribute, Read that file \u2014 it is a photo the sender attached. If the tag has attachment_file_id, call download_attachment with that file_id to fetch the file, then Read the returned path. A single message may carry SEVERAL attachments (a forwarded album or a text+multi-image burst): when attachment_count is set (>1), also handle the numbered siblings \u2014 image_path_2, image_path_3, \u2026 (Read each) and attachment_file_id_2, attachment_file_id_3, \u2026 (download_attachment each). Process every one, not just the first. Reply with the reply tool \u2014 pass chat_id back. The reply tool quote-replies to the latest inbound user message by default, so you do NOT need to pass reply_to for normal responses. Pass reply_to (a message_id) only when quoting a specific earlier message, or pass quote:false to send a bare (non-quoted) message.',
24651
24655
  "",
24652
24656
  `reply accepts file paths (files: ["/abs/path.png"]) for attachments. Use react to add emoji reactions, edit_message for interim progress updates, and delete_message when you need to truly remove a message (prefer edit_message if you just want to change text \u2014 delete is for retraction). Edits don't trigger push notifications \u2014 when a long task completes, send a new reply so the user's device pings. Use send_typing to show a typing indicator during long operations. Use pin_message to pin important outputs. Use forward_message to quote/resurface earlier messages.`,
24653
24657
  "",
@@ -24702,45 +24706,6 @@ var TOOL_SCHEMAS = [
24702
24706
  required: ["chat_id", "text"]
24703
24707
  }
24704
24708
  },
24705
- {
24706
- name: "stream_reply",
24707
- description: "Post the final answer for this turn. The plugin renders an event-driven progress card (Plan \u2192 Run \u2192 Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard cap is 32768 chars (the rich-message wire limit) \u2014 longer text is dropped by a defensive guard, so use `reply` for anything that long (it chunks). Calling with done=false is an error in this environment (the progress card already owns the mid-turn surface). inline_keyboard adds tappable buttons under the final message \u2014 see `reply` for shape and constraints.",
24708
- inputSchema: {
24709
- type: "object",
24710
- properties: {
24711
- chat_id: { type: "string" },
24712
- text: { type: "string", description: "Full text snapshot. NOT a delta \u2014 pass the complete current content each call." },
24713
- done: { type: "boolean", description: "Must be true. Posts this text as the final answer for the turn and locks the message." },
24714
- message_thread_id: { type: "string", description: "Forum topic thread ID. Auto-applied from the last inbound message if not specified." },
24715
- origin_turn_id: { type: "string", description: "In a forum supergroup, pass back the origin_turn_id attribute from the <channel> message you are answering. It pins the reply to that message's topic even if another topic's turn started meanwhile. Omit in DMs / single-topic chats." },
24716
- format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. 'html' (default) converts markdown to Telegram HTML." },
24717
- reply_to: { type: "string", description: "Message ID to quote-reply to. Overrides the default (latest inbound)." },
24718
- quote: { type: "boolean", description: "Opt out of the default quote-reply behavior. Default: true. Ignored when reply_to is explicitly set." },
24719
- protect_content: { type: "boolean", description: "When true, Telegram prevents the message from being forwarded or saved." },
24720
- quote_text: { type: "string", description: "Surgical quote: specific text to highlight from the reply_to message. Requires reply_to." },
24721
- disable_notification: { type: "boolean", description: "When true, the INITIAL message send is silent (no device ping). Has no effect on subsequent edits \u2014 Telegram never pings on editMessageText. Default false. Use for mid-turn stream starts you do not want to ping; omit on the final answer." },
24722
- inline_keyboard: {
24723
- type: "array",
24724
- description: '2D array of tappable buttons under the final message. Same shape and constraints as `reply.inline_keyboard` \u2014 each button has `text` and EXACTLY ONE of `url` or `callback_data`, plus optional `ack_text` (custom tap-toast; default "\u2713 received") and `single_use` (default true; set false to keep the keyboard tappable after a tap). Tap on a callback_data button is delivered to this agent as an inbound channel event with meta.button_callback_data set.',
24725
- items: {
24726
- type: "array",
24727
- items: {
24728
- type: "object",
24729
- properties: {
24730
- text: { type: "string" },
24731
- url: { type: "string" },
24732
- callback_data: { type: "string" },
24733
- ack_text: { type: "string", description: 'Toast text shown on tap. Default "\u2713 received".' },
24734
- single_use: { type: "boolean", description: "Default true. Set false to keep the keyboard tappable after this button is tapped." }
24735
- },
24736
- required: ["text"]
24737
- }
24738
- }
24739
- }
24740
- },
24741
- required: ["chat_id", "text"]
24742
- }
24743
- },
24744
24709
  {
24745
24710
  name: "react",
24746
24711
  description: "Add an emoji reaction to a Telegram message. Telegram only accepts a fixed whitelist (\uD83D\uDC4D \uD83D\uDC4E \u2764 \uD83D\uDD25 \uD83D\uDC40 \uD83C\uDF89 etc) \u2014 non-whitelisted emoji will be rejected.",