@poolzin/pool-bot 2026.2.21 → 2026.2.22

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 (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -29,7 +29,11 @@ const READ_SCOPE = "operator.read";
29
29
  const WRITE_SCOPE = "operator.write";
30
30
  const APPROVALS_SCOPE = "operator.approvals";
31
31
  const PAIRING_SCOPE = "operator.pairing";
32
- const APPROVAL_METHODS = new Set(["exec.approval.request", "exec.approval.resolve"]);
32
+ const APPROVAL_METHODS = new Set([
33
+ "exec.approval.request",
34
+ "exec.approval.waitDecision",
35
+ "exec.approval.resolve",
36
+ ]);
33
37
  const NODE_ROLE_METHODS = new Set(["node.invoke.result", "node.event", "skills.bins"]);
34
38
  const PAIRING_METHODS = new Set([
35
39
  "node.pair.request",
@@ -69,6 +73,8 @@ const READ_METHODS = new Set([
69
73
  "node.list",
70
74
  "node.describe",
71
75
  "chat.history",
76
+ "config.get",
77
+ "talk.config",
72
78
  ]);
73
79
  const WRITE_METHODS = new Set([
74
80
  "send",
@@ -87,13 +93,15 @@ const WRITE_METHODS = new Set([
87
93
  "browser.request",
88
94
  ]);
89
95
  function authorizeGatewayMethod(method, client) {
90
- if (!client?.connect)
96
+ if (!client?.connect) {
91
97
  return null;
98
+ }
92
99
  const role = client.connect.role ?? "operator";
93
100
  const scopes = client.connect.scopes ?? [];
94
101
  if (NODE_ROLE_METHODS.has(method)) {
95
- if (role === "node")
102
+ if (role === "node") {
96
103
  return null;
104
+ }
97
105
  return errorShape(ErrorCodes.INVALID_REQUEST, `unauthorized role: ${role}`);
98
106
  }
99
107
  if (role === "node") {
@@ -102,8 +110,9 @@ function authorizeGatewayMethod(method, client) {
102
110
  if (role !== "operator") {
103
111
  return errorShape(ErrorCodes.INVALID_REQUEST, `unauthorized role: ${role}`);
104
112
  }
105
- if (scopes.includes(ADMIN_SCOPE))
113
+ if (scopes.includes(ADMIN_SCOPE)) {
106
114
  return null;
115
+ }
107
116
  if (APPROVAL_METHODS.has(method) && !scopes.includes(APPROVALS_SCOPE)) {
108
117
  return errorShape(ErrorCodes.INVALID_REQUEST, "missing scope: operator.approvals");
109
118
  }
@@ -116,14 +125,18 @@ function authorizeGatewayMethod(method, client) {
116
125
  if (WRITE_METHODS.has(method) && !scopes.includes(WRITE_SCOPE)) {
117
126
  return errorShape(ErrorCodes.INVALID_REQUEST, "missing scope: operator.write");
118
127
  }
119
- if (APPROVAL_METHODS.has(method))
128
+ if (APPROVAL_METHODS.has(method)) {
120
129
  return null;
121
- if (PAIRING_METHODS.has(method))
130
+ }
131
+ if (PAIRING_METHODS.has(method)) {
122
132
  return null;
123
- if (READ_METHODS.has(method))
133
+ }
134
+ if (READ_METHODS.has(method)) {
124
135
  return null;
125
- if (WRITE_METHODS.has(method))
136
+ }
137
+ if (WRITE_METHODS.has(method)) {
126
138
  return null;
139
+ }
127
140
  if (ADMIN_METHOD_PREFIXES.some((prefix) => method.startsWith(prefix))) {
128
141
  return errorShape(ErrorCodes.INVALID_REQUEST, "missing scope: operator.admin");
129
142
  }
@@ -131,6 +144,9 @@ function authorizeGatewayMethod(method, client) {
131
144
  method.startsWith("wizard.") ||
132
145
  method.startsWith("update.") ||
133
146
  method === "channels.logout" ||
147
+ method === "agents.create" ||
148
+ method === "agents.update" ||
149
+ method === "agents.delete" ||
134
150
  method === "skills.install" ||
135
151
  method === "skills.update" ||
136
152
  method === "cron.add" ||
@@ -1,19 +1,183 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { resolveSessionAgentId } from "../agents/agent-scope.js";
2
3
  import { normalizeChannelId } from "../channels/plugins/index.js";
4
+ import { createOutboundSendDeps } from "../cli/outbound-send-deps.js";
3
5
  import { agentCommand } from "../commands/agent.js";
4
6
  import { loadConfig } from "../config/config.js";
5
7
  import { updateSessionStore } from "../config/sessions.js";
6
8
  import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
9
+ import { deliverOutboundPayloads } from "../infra/outbound/deliver.js";
10
+ import { resolveOutboundTarget } from "../infra/outbound/targets.js";
7
11
  import { enqueueSystemEvent } from "../infra/system-events.js";
8
12
  import { normalizeMainKey } from "../routing/session-key.js";
9
13
  import { defaultRuntime } from "../runtime.js";
10
- import { loadSessionEntry } from "./session-utils.js";
14
+ import { parseMessageWithAttachments } from "./chat-attachments.js";
15
+ import { normalizeRpcAttachmentsToChatAttachments } from "./server-methods/attachment-normalize.js";
16
+ import { loadSessionEntry, pruneLegacyStoreKeys, resolveGatewaySessionStoreTarget, } from "./session-utils.js";
11
17
  import { formatForLog } from "./ws-log.js";
18
+ const MAX_EXEC_EVENT_OUTPUT_CHARS = 180;
19
+ const VOICE_TRANSCRIPT_DEDUPE_WINDOW_MS = 1500;
20
+ const MAX_RECENT_VOICE_TRANSCRIPTS = 200;
21
+ const recentVoiceTranscripts = new Map();
22
+ function normalizeNonEmptyString(value) {
23
+ if (typeof value !== "string") {
24
+ return null;
25
+ }
26
+ const trimmed = value.trim();
27
+ return trimmed.length > 0 ? trimmed : null;
28
+ }
29
+ function normalizeFiniteInteger(value) {
30
+ return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : null;
31
+ }
32
+ function resolveVoiceTranscriptFingerprint(obj, text) {
33
+ const eventId = normalizeNonEmptyString(obj.eventId) ??
34
+ normalizeNonEmptyString(obj.providerEventId) ??
35
+ normalizeNonEmptyString(obj.transcriptId);
36
+ if (eventId) {
37
+ return `event:${eventId}`;
38
+ }
39
+ const callId = normalizeNonEmptyString(obj.providerCallId) ?? normalizeNonEmptyString(obj.callId);
40
+ const sequence = normalizeFiniteInteger(obj.sequence) ?? normalizeFiniteInteger(obj.seq);
41
+ if (callId && sequence !== null) {
42
+ return `call-seq:${callId}:${sequence}`;
43
+ }
44
+ const eventTimestamp = normalizeFiniteInteger(obj.timestamp) ??
45
+ normalizeFiniteInteger(obj.ts) ??
46
+ normalizeFiniteInteger(obj.eventTimestamp);
47
+ if (callId && eventTimestamp !== null) {
48
+ return `call-ts:${callId}:${eventTimestamp}`;
49
+ }
50
+ if (eventTimestamp !== null) {
51
+ return `timestamp:${eventTimestamp}|text:${text}`;
52
+ }
53
+ return `text:${text}`;
54
+ }
55
+ function shouldDropDuplicateVoiceTranscript(params) {
56
+ const previous = recentVoiceTranscripts.get(params.sessionKey);
57
+ if (previous &&
58
+ previous.fingerprint === params.fingerprint &&
59
+ params.now - previous.ts <= VOICE_TRANSCRIPT_DEDUPE_WINDOW_MS) {
60
+ return true;
61
+ }
62
+ recentVoiceTranscripts.set(params.sessionKey, {
63
+ fingerprint: params.fingerprint,
64
+ ts: params.now,
65
+ });
66
+ if (recentVoiceTranscripts.size > MAX_RECENT_VOICE_TRANSCRIPTS) {
67
+ const cutoff = params.now - VOICE_TRANSCRIPT_DEDUPE_WINDOW_MS * 2;
68
+ for (const [key, value] of recentVoiceTranscripts) {
69
+ if (value.ts < cutoff) {
70
+ recentVoiceTranscripts.delete(key);
71
+ }
72
+ if (recentVoiceTranscripts.size <= MAX_RECENT_VOICE_TRANSCRIPTS) {
73
+ break;
74
+ }
75
+ }
76
+ while (recentVoiceTranscripts.size > MAX_RECENT_VOICE_TRANSCRIPTS) {
77
+ const oldestKey = recentVoiceTranscripts.keys().next().value;
78
+ if (oldestKey === undefined) {
79
+ break;
80
+ }
81
+ recentVoiceTranscripts.delete(oldestKey);
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+ function compactExecEventOutput(raw) {
87
+ const normalized = raw.replace(/\s+/g, " ").trim();
88
+ if (!normalized) {
89
+ return "";
90
+ }
91
+ if (normalized.length <= MAX_EXEC_EVENT_OUTPUT_CHARS) {
92
+ return normalized;
93
+ }
94
+ const safe = Math.max(1, MAX_EXEC_EVENT_OUTPUT_CHARS - 1);
95
+ return `${normalized.slice(0, safe)}…`;
96
+ }
97
+ async function touchSessionStore(params) {
98
+ const { storePath } = params;
99
+ if (!storePath) {
100
+ return;
101
+ }
102
+ await updateSessionStore(storePath, (store) => {
103
+ const target = resolveGatewaySessionStoreTarget({
104
+ cfg: params.cfg,
105
+ key: params.sessionKey,
106
+ store,
107
+ });
108
+ pruneLegacyStoreKeys({
109
+ store,
110
+ canonicalKey: target.canonicalKey,
111
+ candidates: target.storeKeys,
112
+ });
113
+ store[params.canonicalKey] = {
114
+ sessionId: params.sessionId,
115
+ updatedAt: params.now,
116
+ thinkingLevel: params.entry?.thinkingLevel,
117
+ verboseLevel: params.entry?.verboseLevel,
118
+ reasoningLevel: params.entry?.reasoningLevel,
119
+ systemSent: params.entry?.systemSent,
120
+ sendPolicy: params.entry?.sendPolicy,
121
+ lastChannel: params.entry?.lastChannel,
122
+ lastTo: params.entry?.lastTo,
123
+ };
124
+ });
125
+ }
126
+ function queueSessionStoreTouch(params) {
127
+ void touchSessionStore({
128
+ cfg: params.cfg,
129
+ sessionKey: params.sessionKey,
130
+ storePath: params.storePath,
131
+ canonicalKey: params.canonicalKey,
132
+ entry: params.entry,
133
+ sessionId: params.sessionId,
134
+ now: params.now,
135
+ }).catch((err) => {
136
+ params.ctx.logGateway.warn("voice session-store update failed: " + formatForLog(err));
137
+ });
138
+ }
139
+ function parseSessionKeyFromPayloadJSON(payloadJSON) {
140
+ let payload;
141
+ try {
142
+ payload = JSON.parse(payloadJSON);
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ if (typeof payload !== "object" || payload === null) {
148
+ return null;
149
+ }
150
+ const obj = payload;
151
+ const sessionKey = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
152
+ return sessionKey.length > 0 ? sessionKey : null;
153
+ }
154
+ async function sendReceiptAck(params) {
155
+ const resolved = resolveOutboundTarget({
156
+ channel: params.channel,
157
+ to: params.to,
158
+ cfg: params.cfg,
159
+ mode: "explicit",
160
+ });
161
+ if (!resolved.ok) {
162
+ throw new Error(String(resolved.error));
163
+ }
164
+ const agentId = resolveSessionAgentId({ sessionKey: params.sessionKey, config: params.cfg });
165
+ await deliverOutboundPayloads({
166
+ cfg: params.cfg,
167
+ channel: params.channel,
168
+ to: resolved.to,
169
+ payloads: [{ text: params.text }],
170
+ agentId,
171
+ bestEffort: true,
172
+ deps: createOutboundSendDeps(params.deps),
173
+ });
174
+ }
12
175
  export const handleNodeEvent = async (ctx, nodeId, evt) => {
13
176
  switch (evt.event) {
14
177
  case "voice.transcript": {
15
- if (!evt.payloadJSON)
178
+ if (!evt.payloadJSON) {
16
179
  return;
180
+ }
17
181
  let payload;
18
182
  try {
19
183
  payload = JSON.parse(evt.payloadJSON);
@@ -23,53 +187,60 @@ export const handleNodeEvent = async (ctx, nodeId, evt) => {
23
187
  }
24
188
  const obj = typeof payload === "object" && payload !== null ? payload : {};
25
189
  const text = typeof obj.text === "string" ? obj.text.trim() : "";
26
- if (!text)
190
+ if (!text) {
27
191
  return;
28
- if (text.length > 20_000)
192
+ }
193
+ if (text.length > 20_000) {
29
194
  return;
195
+ }
30
196
  const sessionKeyRaw = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
31
197
  const cfg = loadConfig();
32
198
  const rawMainKey = normalizeMainKey(cfg.session?.mainKey);
33
199
  const sessionKey = sessionKeyRaw.length > 0 ? sessionKeyRaw : rawMainKey;
34
200
  const { storePath, entry, canonicalKey } = loadSessionEntry(sessionKey);
35
201
  const now = Date.now();
36
- const sessionId = entry?.sessionId ?? randomUUID();
37
- if (storePath) {
38
- await updateSessionStore(storePath, (store) => {
39
- store[canonicalKey] = {
40
- sessionId,
41
- updatedAt: now,
42
- thinkingLevel: entry?.thinkingLevel,
43
- verboseLevel: entry?.verboseLevel,
44
- reasoningLevel: entry?.reasoningLevel,
45
- systemSent: entry?.systemSent,
46
- sendPolicy: entry?.sendPolicy,
47
- lastChannel: entry?.lastChannel,
48
- lastTo: entry?.lastTo,
49
- };
50
- });
202
+ const fingerprint = resolveVoiceTranscriptFingerprint(obj, text);
203
+ if (shouldDropDuplicateVoiceTranscript({ sessionKey: canonicalKey, fingerprint, now })) {
204
+ return;
51
205
  }
206
+ const sessionId = entry?.sessionId ?? randomUUID();
207
+ queueSessionStoreTouch({
208
+ ctx,
209
+ cfg,
210
+ sessionKey,
211
+ storePath,
212
+ canonicalKey,
213
+ entry,
214
+ sessionId,
215
+ now,
216
+ });
52
217
  // Ensure chat UI clients refresh when this run completes (even though it wasn't started via chat.send).
53
218
  // This maps agent bus events (keyed by sessionId) to chat events (keyed by clientRunId).
54
219
  ctx.addChatRun(sessionId, {
55
- sessionKey,
220
+ sessionKey: canonicalKey,
56
221
  clientRunId: `voice-${randomUUID()}`,
57
222
  });
58
223
  void agentCommand({
59
224
  message: text,
60
225
  sessionId,
61
- sessionKey,
226
+ sessionKey: canonicalKey,
62
227
  thinking: "low",
63
228
  deliver: false,
64
229
  messageChannel: "node",
230
+ inputProvenance: {
231
+ kind: "external_user",
232
+ sourceChannel: "voice",
233
+ sourceTool: "gateway.voice.transcript",
234
+ },
65
235
  }, defaultRuntime, ctx.deps).catch((err) => {
66
236
  ctx.logGateway.warn(`agent failed node=${nodeId}: ${formatForLog(err)}`);
67
237
  });
68
238
  return;
69
239
  }
70
240
  case "agent.request": {
71
- if (!evt.payloadJSON)
241
+ if (!evt.payloadJSON) {
72
242
  return;
243
+ }
73
244
  let link = null;
74
245
  try {
75
246
  link = JSON.parse(evt.payloadJSON);
@@ -77,43 +248,84 @@ export const handleNodeEvent = async (ctx, nodeId, evt) => {
77
248
  catch {
78
249
  return;
79
250
  }
80
- const message = (link?.message ?? "").trim();
81
- if (!message)
251
+ let message = (link?.message ?? "").trim();
252
+ const normalizedAttachments = normalizeRpcAttachmentsToChatAttachments(link?.attachments ?? undefined);
253
+ let images = [];
254
+ if (normalizedAttachments.length > 0) {
255
+ try {
256
+ const parsed = await parseMessageWithAttachments(message, normalizedAttachments, {
257
+ maxBytes: 5_000_000,
258
+ log: ctx.logGateway,
259
+ });
260
+ message = parsed.message.trim();
261
+ images = parsed.images;
262
+ }
263
+ catch {
264
+ return;
265
+ }
266
+ }
267
+ if (!message) {
82
268
  return;
83
- if (message.length > 20_000)
269
+ }
270
+ if (message.length > 20_000) {
84
271
  return;
272
+ }
85
273
  const channelRaw = typeof link?.channel === "string" ? link.channel.trim() : "";
86
- const channel = normalizeChannelId(channelRaw) ?? undefined;
87
- const to = typeof link?.to === "string" && link.to.trim() ? link.to.trim() : undefined;
88
- const deliver = Boolean(link?.deliver) && Boolean(channel);
274
+ let channel = normalizeChannelId(channelRaw) ?? undefined;
275
+ let to = typeof link?.to === "string" && link.to.trim() ? link.to.trim() : undefined;
276
+ const deliverRequested = Boolean(link?.deliver);
277
+ const wantsReceipt = Boolean(link?.receipt);
278
+ const receiptTextRaw = typeof link?.receiptText === "string" ? link.receiptText.trim() : "";
279
+ const receiptText = receiptTextRaw || "Just received your iOS share + request, working on it.";
89
280
  const sessionKeyRaw = (link?.sessionKey ?? "").trim();
90
281
  const sessionKey = sessionKeyRaw.length > 0 ? sessionKeyRaw : `node-${nodeId}`;
282
+ const cfg = loadConfig();
91
283
  const { storePath, entry, canonicalKey } = loadSessionEntry(sessionKey);
92
284
  const now = Date.now();
93
285
  const sessionId = entry?.sessionId ?? randomUUID();
94
- if (storePath) {
95
- await updateSessionStore(storePath, (store) => {
96
- store[canonicalKey] = {
97
- sessionId,
98
- updatedAt: now,
99
- thinkingLevel: entry?.thinkingLevel,
100
- verboseLevel: entry?.verboseLevel,
101
- reasoningLevel: entry?.reasoningLevel,
102
- systemSent: entry?.systemSent,
103
- sendPolicy: entry?.sendPolicy,
104
- lastChannel: entry?.lastChannel,
105
- lastTo: entry?.lastTo,
106
- };
286
+ await touchSessionStore({ cfg, sessionKey, storePath, canonicalKey, entry, sessionId, now });
287
+ if (deliverRequested && (!channel || !to)) {
288
+ const entryChannel = typeof entry?.lastChannel === "string"
289
+ ? normalizeChannelId(entry.lastChannel)
290
+ : undefined;
291
+ const entryTo = typeof entry?.lastTo === "string" ? entry.lastTo.trim() : "";
292
+ if (!channel && entryChannel) {
293
+ channel = entryChannel;
294
+ }
295
+ if (!to && entryTo) {
296
+ to = entryTo;
297
+ }
298
+ }
299
+ const deliver = deliverRequested && Boolean(channel && to);
300
+ const deliveryChannel = deliver ? channel : undefined;
301
+ const deliveryTo = deliver ? to : undefined;
302
+ if (deliverRequested && !deliver) {
303
+ ctx.logGateway.warn(`agent delivery disabled node=${nodeId}: missing session delivery route (channel=${channel ?? "-"} to=${to ?? "-"})`);
304
+ }
305
+ if (wantsReceipt && deliveryChannel && deliveryTo) {
306
+ void sendReceiptAck({
307
+ cfg,
308
+ deps: ctx.deps,
309
+ sessionKey: canonicalKey,
310
+ channel: deliveryChannel,
311
+ to: deliveryTo,
312
+ text: receiptText,
313
+ }).catch((err) => {
314
+ ctx.logGateway.warn(`agent receipt failed node=${nodeId}: ${formatForLog(err)}`);
107
315
  });
108
316
  }
317
+ else if (wantsReceipt) {
318
+ ctx.logGateway.warn(`agent receipt skipped node=${nodeId}: missing delivery route (channel=${deliveryChannel ?? "-"} to=${deliveryTo ?? "-"})`);
319
+ }
109
320
  void agentCommand({
110
321
  message,
322
+ images,
111
323
  sessionId,
112
- sessionKey,
324
+ sessionKey: canonicalKey,
113
325
  thinking: link?.thinking ?? undefined,
114
326
  deliver,
115
- to,
116
- channel,
327
+ to: deliveryTo,
328
+ channel: deliveryChannel,
117
329
  timeout: typeof link?.timeoutSeconds === "number" ? link.timeoutSeconds.toString() : undefined,
118
330
  messageChannel: "node",
119
331
  }, defaultRuntime, ctx.deps).catch((err) => {
@@ -122,44 +334,33 @@ export const handleNodeEvent = async (ctx, nodeId, evt) => {
122
334
  return;
123
335
  }
124
336
  case "chat.subscribe": {
125
- if (!evt.payloadJSON)
337
+ if (!evt.payloadJSON) {
126
338
  return;
127
- let payload;
128
- try {
129
- payload = JSON.parse(evt.payloadJSON);
130
339
  }
131
- catch {
340
+ const sessionKey = parseSessionKeyFromPayloadJSON(evt.payloadJSON);
341
+ if (!sessionKey) {
132
342
  return;
133
343
  }
134
- const obj = typeof payload === "object" && payload !== null ? payload : {};
135
- const sessionKey = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
136
- if (!sessionKey)
137
- return;
138
344
  ctx.nodeSubscribe(nodeId, sessionKey);
139
345
  return;
140
346
  }
141
347
  case "chat.unsubscribe": {
142
- if (!evt.payloadJSON)
348
+ if (!evt.payloadJSON) {
143
349
  return;
144
- let payload;
145
- try {
146
- payload = JSON.parse(evt.payloadJSON);
147
350
  }
148
- catch {
351
+ const sessionKey = parseSessionKeyFromPayloadJSON(evt.payloadJSON);
352
+ if (!sessionKey) {
149
353
  return;
150
354
  }
151
- const obj = typeof payload === "object" && payload !== null ? payload : {};
152
- const sessionKey = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
153
- if (!sessionKey)
154
- return;
155
355
  ctx.nodeUnsubscribe(nodeId, sessionKey);
156
356
  return;
157
357
  }
158
358
  case "exec.started":
159
359
  case "exec.finished":
160
360
  case "exec.denied": {
161
- if (!evt.payloadJSON)
361
+ if (!evt.payloadJSON) {
162
362
  return;
363
+ }
163
364
  let payload;
164
365
  try {
165
366
  payload = JSON.parse(evt.payloadJSON);
@@ -169,8 +370,9 @@ export const handleNodeEvent = async (ctx, nodeId, evt) => {
169
370
  }
170
371
  const obj = typeof payload === "object" && payload !== null ? payload : {};
171
372
  const sessionKey = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : `node-${nodeId}`;
172
- if (!sessionKey)
373
+ if (!sessionKey) {
173
374
  return;
375
+ }
174
376
  const runId = typeof obj.runId === "string" ? obj.runId.trim() : "";
175
377
  const command = typeof obj.command === "string" ? obj.command.trim() : "";
176
378
  const exitCode = typeof obj.exitCode === "number" && Number.isFinite(obj.exitCode)
@@ -182,19 +384,27 @@ export const handleNodeEvent = async (ctx, nodeId, evt) => {
182
384
  let text = "";
183
385
  if (evt.event === "exec.started") {
184
386
  text = `Exec started (node=${nodeId}${runId ? ` id=${runId}` : ""})`;
185
- if (command)
387
+ if (command) {
186
388
  text += `: ${command}`;
389
+ }
187
390
  }
188
391
  else if (evt.event === "exec.finished") {
189
392
  const exitLabel = timedOut ? "timeout" : `code ${exitCode ?? "?"}`;
393
+ const compactOutput = compactExecEventOutput(output);
394
+ const shouldNotify = timedOut || exitCode !== 0 || compactOutput.length > 0;
395
+ if (!shouldNotify) {
396
+ return;
397
+ }
190
398
  text = `Exec finished (node=${nodeId}${runId ? ` id=${runId}` : ""}, ${exitLabel})`;
191
- if (output)
192
- text += `\n${output}`;
399
+ if (compactOutput) {
400
+ text += `\n${compactOutput}`;
401
+ }
193
402
  }
194
403
  else {
195
404
  text = `Exec denied (node=${nodeId}${runId ? ` id=${runId}` : ""}${reason ? `, ${reason}` : ""})`;
196
- if (command)
405
+ if (command) {
197
406
  text += `: ${command}`;
407
+ }
198
408
  }
199
409
  enqueueSystemEvent(text, { sessionKey, contextKey: runId ? `exec:${runId}` : "exec" });
200
410
  requestHeartbeatNow({ reason: "exec-event" });