@poolzin/pool-bot 2026.2.21 → 2026.2.23

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 (378) hide show
  1. package/CHANGELOG.md +25 -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/device-pair/index.ts +2 -2
  326. package/extensions/diagnostics-otel/package.json +1 -1
  327. package/extensions/discord/package.json +1 -1
  328. package/extensions/feishu/package.json +1 -1
  329. package/extensions/google-antigravity-auth/package.json +1 -1
  330. package/extensions/google-gemini-cli-auth/package.json +1 -1
  331. package/extensions/googlechat/package.json +1 -1
  332. package/extensions/imessage/package.json +1 -1
  333. package/extensions/irc/package.json +1 -1
  334. package/extensions/irc/src/accounts.ts +1 -1
  335. package/extensions/irc/src/onboarding.ts +4 -4
  336. package/extensions/line/package.json +1 -1
  337. package/extensions/llm-task/package.json +1 -1
  338. package/extensions/lobster/package.json +1 -1
  339. package/extensions/matrix/CHANGELOG.md +10 -0
  340. package/extensions/matrix/package.json +1 -1
  341. package/extensions/mattermost/package.json +1 -1
  342. package/extensions/memory-core/package.json +1 -1
  343. package/extensions/memory-lancedb/package.json +1 -1
  344. package/extensions/minimax-portal-auth/package.json +1 -1
  345. package/extensions/msteams/CHANGELOG.md +10 -0
  346. package/extensions/msteams/package.json +1 -1
  347. package/extensions/nextcloud-talk/package.json +1 -1
  348. package/extensions/nostr/CHANGELOG.md +10 -0
  349. package/extensions/nostr/package.json +1 -1
  350. package/extensions/open-prose/package.json +1 -1
  351. package/extensions/openai-codex-auth/package.json +1 -1
  352. package/extensions/signal/package.json +1 -1
  353. package/extensions/slack/package.json +1 -1
  354. package/extensions/telegram/package.json +1 -1
  355. package/extensions/tlon/package.json +1 -1
  356. package/extensions/twitch/CHANGELOG.md +10 -0
  357. package/extensions/twitch/package.json +1 -1
  358. package/extensions/voice-call/CHANGELOG.md +10 -0
  359. package/extensions/voice-call/package.json +1 -1
  360. package/extensions/whatsapp/package.json +1 -1
  361. package/extensions/zalo/CHANGELOG.md +10 -0
  362. package/extensions/zalo/package.json +1 -1
  363. package/extensions/zalouser/CHANGELOG.md +10 -0
  364. package/extensions/zalouser/package.json +1 -1
  365. package/package.json +1 -1
  366. package/skills/apple-reminders/SKILL.md +100 -49
  367. package/skills/coding-agent/SKILL.md +34 -28
  368. package/skills/github/SKILL.md +131 -16
  369. package/skills/imsg/SKILL.md +112 -15
  370. package/skills/openhue/SKILL.md +101 -19
  371. package/skills/tmux/SKILL.md +111 -79
  372. package/skills/weather/SKILL.md +88 -25
  373. package/dist/agents/openclaw-tools.js +0 -151
  374. package/dist/agents/tool-security.js +0 -96
  375. package/dist/gateway/url-validation.js +0 -94
  376. package/dist/infra/openclaw-root.js +0 -109
  377. package/dist/infra/tmp-openclaw-dir.js +0 -81
  378. package/dist/media/path-sanitization.js +0 -78
@@ -1,18 +1,19 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { listChannelMessageActions, supportsChannelMessageButtons, supportsChannelMessageCards, } from "../../channels/plugins/message-actions.js";
3
- import { CHANNEL_MESSAGE_ACTION_NAMES, } from "../../channels/plugins/types.js";
4
2
  import { BLUEBUBBLES_GROUP_ACTIONS } from "../../channels/plugins/bluebubbles-actions.js";
3
+ import { listChannelMessageActions, supportsChannelMessageButtons, supportsChannelMessageButtonsForChannel, supportsChannelMessageCards, supportsChannelMessageCardsForChannel, } from "../../channels/plugins/message-actions.js";
4
+ import { CHANNEL_MESSAGE_ACTION_NAMES, } from "../../channels/plugins/types.js";
5
5
  import { loadConfig } from "../../config/config.js";
6
6
  import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES } from "../../gateway/protocol/client-info.js";
7
- import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
8
7
  import { getToolResult, runMessageAction } from "../../infra/outbound/message-action-runner.js";
9
- import { resolveSessionAgentId } from "../agent-scope.js";
8
+ import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
10
9
  import { normalizeAccountId } from "../../routing/session-key.js";
11
- import { channelTargetSchema, channelTargetsSchema, stringEnum } from "../schema/typebox.js";
12
- import { listChannelSupportedActions } from "../channel-tools.js";
13
- import { normalizeMessageChannel } from "../../utils/message-channel.js";
14
10
  import { stripReasoningTagsFromText } from "../../shared/text/reasoning-tags.js";
11
+ import { normalizeMessageChannel } from "../../utils/message-channel.js";
12
+ import { resolveSessionAgentId } from "../agent-scope.js";
13
+ import { listChannelSupportedActions } from "../channel-tools.js";
14
+ import { channelTargetSchema, channelTargetsSchema, stringEnum } from "../schema/typebox.js";
15
15
  import { jsonResult, readNumberParam, readStringParam } from "./common.js";
16
+ import { resolveGatewayOptions } from "./gateway.js";
16
17
  const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES;
17
18
  const EXPLICIT_TARGET_ACTIONS = new Set([
18
19
  "send",
@@ -34,6 +35,90 @@ function buildRoutingSchema() {
34
35
  dryRun: Type.Optional(Type.Boolean()),
35
36
  };
36
37
  }
38
+ const discordComponentEmojiSchema = Type.Object({
39
+ name: Type.String(),
40
+ id: Type.Optional(Type.String()),
41
+ animated: Type.Optional(Type.Boolean()),
42
+ });
43
+ const discordComponentOptionSchema = Type.Object({
44
+ label: Type.String(),
45
+ value: Type.String(),
46
+ description: Type.Optional(Type.String()),
47
+ emoji: Type.Optional(discordComponentEmojiSchema),
48
+ default: Type.Optional(Type.Boolean()),
49
+ });
50
+ const discordComponentButtonSchema = Type.Object({
51
+ label: Type.String(),
52
+ style: Type.Optional(stringEnum(["primary", "secondary", "success", "danger", "link"])),
53
+ url: Type.Optional(Type.String()),
54
+ emoji: Type.Optional(discordComponentEmojiSchema),
55
+ disabled: Type.Optional(Type.Boolean()),
56
+ allowedUsers: Type.Optional(Type.Array(Type.String({
57
+ description: "Discord user ids or names allowed to interact with this button.",
58
+ }))),
59
+ });
60
+ const discordComponentSelectSchema = Type.Object({
61
+ type: Type.Optional(stringEnum(["string", "user", "role", "mentionable", "channel"])),
62
+ placeholder: Type.Optional(Type.String()),
63
+ minValues: Type.Optional(Type.Number()),
64
+ maxValues: Type.Optional(Type.Number()),
65
+ options: Type.Optional(Type.Array(discordComponentOptionSchema)),
66
+ });
67
+ const discordComponentBlockSchema = Type.Object({
68
+ type: Type.String(),
69
+ text: Type.Optional(Type.String()),
70
+ texts: Type.Optional(Type.Array(Type.String())),
71
+ accessory: Type.Optional(Type.Object({
72
+ type: Type.String(),
73
+ url: Type.Optional(Type.String()),
74
+ button: Type.Optional(discordComponentButtonSchema),
75
+ })),
76
+ spacing: Type.Optional(stringEnum(["small", "large"])),
77
+ divider: Type.Optional(Type.Boolean()),
78
+ buttons: Type.Optional(Type.Array(discordComponentButtonSchema)),
79
+ select: Type.Optional(discordComponentSelectSchema),
80
+ items: Type.Optional(Type.Array(Type.Object({
81
+ url: Type.String(),
82
+ description: Type.Optional(Type.String()),
83
+ spoiler: Type.Optional(Type.Boolean()),
84
+ }))),
85
+ file: Type.Optional(Type.String()),
86
+ spoiler: Type.Optional(Type.Boolean()),
87
+ });
88
+ const discordComponentModalFieldSchema = Type.Object({
89
+ type: Type.String(),
90
+ name: Type.Optional(Type.String()),
91
+ label: Type.String(),
92
+ description: Type.Optional(Type.String()),
93
+ placeholder: Type.Optional(Type.String()),
94
+ required: Type.Optional(Type.Boolean()),
95
+ options: Type.Optional(Type.Array(discordComponentOptionSchema)),
96
+ minValues: Type.Optional(Type.Number()),
97
+ maxValues: Type.Optional(Type.Number()),
98
+ minLength: Type.Optional(Type.Number()),
99
+ maxLength: Type.Optional(Type.Number()),
100
+ style: Type.Optional(stringEnum(["short", "paragraph"])),
101
+ });
102
+ const discordComponentModalSchema = Type.Object({
103
+ title: Type.String(),
104
+ triggerLabel: Type.Optional(Type.String()),
105
+ triggerStyle: Type.Optional(stringEnum(["primary", "secondary", "success", "danger", "link"])),
106
+ fields: Type.Array(discordComponentModalFieldSchema),
107
+ });
108
+ const discordComponentMessageSchema = Type.Object({
109
+ text: Type.Optional(Type.String()),
110
+ reusable: Type.Optional(Type.Boolean({
111
+ description: "Allow components to be used multiple times until they expire.",
112
+ })),
113
+ container: Type.Optional(Type.Object({
114
+ accentColor: Type.Optional(Type.String()),
115
+ spoiler: Type.Optional(Type.Boolean()),
116
+ })),
117
+ blocks: Type.Optional(Type.Array(discordComponentBlockSchema)),
118
+ modal: Type.Optional(discordComponentModalSchema),
119
+ }, {
120
+ description: "Discord components v2 payload. Set reusable=true to keep buttons, selects, and forms active until expiry.",
121
+ });
37
122
  function buildSendSchema(options) {
38
123
  const props = {
39
124
  message: Type.Optional(Type.String()),
@@ -57,12 +142,13 @@ function buildSendSchema(options) {
57
142
  threadId: Type.Optional(Type.String()),
58
143
  asVoice: Type.Optional(Type.Boolean()),
59
144
  silent: Type.Optional(Type.Boolean()),
145
+ quoteText: Type.Optional(Type.String({ description: "Quote text for Telegram reply_parameters" })),
60
146
  bestEffort: Type.Optional(Type.Boolean()),
61
147
  gifPlayback: Type.Optional(Type.Boolean()),
62
- quoteText: Type.Optional(Type.String({ description: "Quote text for Telegram reply_parameters" })),
63
148
  buttons: Type.Optional(Type.Array(Type.Array(Type.Object({
64
149
  text: Type.String(),
65
150
  callback_data: Type.String(),
151
+ style: Type.Optional(stringEnum(["danger", "success", "primary"])),
66
152
  })), {
67
153
  description: "Telegram inline keyboard buttons (array of button rows)",
68
154
  })),
@@ -70,11 +156,17 @@ function buildSendSchema(options) {
70
156
  additionalProperties: true,
71
157
  description: "Adaptive Card JSON object (when supported by the channel)",
72
158
  })),
159
+ components: Type.Optional(discordComponentMessageSchema),
73
160
  };
74
- if (!options.includeButtons)
161
+ if (!options.includeButtons) {
75
162
  delete props.buttons;
76
- if (!options.includeCards)
163
+ }
164
+ if (!options.includeCards) {
77
165
  delete props.card;
166
+ }
167
+ if (!options.includeComponents) {
168
+ delete props.components;
169
+ }
78
170
  return props;
79
171
  }
80
172
  function buildReactionSchema() {
@@ -159,21 +251,6 @@ function buildGatewaySchema() {
159
251
  timeoutMs: Type.Optional(Type.Number()),
160
252
  };
161
253
  }
162
- function buildChannelManagementSchema() {
163
- return {
164
- name: Type.Optional(Type.String()),
165
- type: Type.Optional(Type.Number()),
166
- parentId: Type.Optional(Type.String()),
167
- topic: Type.Optional(Type.String()),
168
- position: Type.Optional(Type.Number()),
169
- nsfw: Type.Optional(Type.Boolean()),
170
- rateLimitPerUser: Type.Optional(Type.Number()),
171
- categoryId: Type.Optional(Type.String()),
172
- clearParent: Type.Optional(Type.Boolean({
173
- description: "Clear the parent/category when supported by the provider.",
174
- })),
175
- };
176
- }
177
254
  function buildPresenceSchema() {
178
255
  return {
179
256
  activityType: Type.Optional(Type.String({
@@ -191,6 +268,21 @@ function buildPresenceSchema() {
191
268
  status: Type.Optional(Type.String({ description: "Bot status: online, dnd, idle, invisible." })),
192
269
  };
193
270
  }
271
+ function buildChannelManagementSchema() {
272
+ return {
273
+ name: Type.Optional(Type.String()),
274
+ type: Type.Optional(Type.Number()),
275
+ parentId: Type.Optional(Type.String()),
276
+ topic: Type.Optional(Type.String()),
277
+ position: Type.Optional(Type.Number()),
278
+ nsfw: Type.Optional(Type.Boolean()),
279
+ rateLimitPerUser: Type.Optional(Type.Number()),
280
+ categoryId: Type.Optional(Type.String()),
281
+ clearParent: Type.Optional(Type.Boolean({
282
+ description: "Clear the parent/category when supported by the provider.",
283
+ })),
284
+ };
285
+ }
194
286
  function buildMessageToolSchemaProps(options) {
195
287
  return {
196
288
  ...buildRoutingSchema(),
@@ -218,37 +310,74 @@ function buildMessageToolSchemaFromActions(actions, options) {
218
310
  const MessageToolSchema = buildMessageToolSchemaFromActions(AllMessageActions, {
219
311
  includeButtons: true,
220
312
  includeCards: true,
313
+ includeComponents: true,
221
314
  });
222
- function buildMessageToolSchema(cfg) {
223
- const actions = listChannelMessageActions(cfg);
224
- const includeButtons = supportsChannelMessageButtons(cfg);
225
- const includeCards = supportsChannelMessageCards(cfg);
315
+ function resolveMessageToolSchemaActions(params) {
316
+ const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
317
+ if (currentChannel) {
318
+ const scopedActions = filterActionsForContext({
319
+ actions: listChannelSupportedActions({
320
+ cfg: params.cfg,
321
+ channel: currentChannel,
322
+ }),
323
+ channel: currentChannel,
324
+ currentChannelId: params.currentChannelId,
325
+ });
326
+ const withSend = new Set(["send", ...scopedActions]);
327
+ return Array.from(withSend);
328
+ }
329
+ const actions = listChannelMessageActions(params.cfg);
330
+ return actions.length > 0 ? actions : ["send"];
331
+ }
332
+ function resolveIncludeComponents(params) {
333
+ const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
334
+ if (currentChannel) {
335
+ return currentChannel === "discord";
336
+ }
337
+ // Components are currently Discord-specific.
338
+ return listChannelSupportedActions({ cfg: params.cfg, channel: "discord" }).length > 0;
339
+ }
340
+ function buildMessageToolSchema(params) {
341
+ const currentChannel = normalizeMessageChannel(params.currentChannelProvider);
342
+ const actions = resolveMessageToolSchemaActions(params);
343
+ const includeButtons = currentChannel
344
+ ? supportsChannelMessageButtonsForChannel({ cfg: params.cfg, channel: currentChannel })
345
+ : supportsChannelMessageButtons(params.cfg);
346
+ const includeCards = currentChannel
347
+ ? supportsChannelMessageCardsForChannel({ cfg: params.cfg, channel: currentChannel })
348
+ : supportsChannelMessageCards(params.cfg);
349
+ const includeComponents = resolveIncludeComponents(params);
226
350
  return buildMessageToolSchemaFromActions(actions.length > 0 ? actions : ["send"], {
227
351
  includeButtons,
228
352
  includeCards,
353
+ includeComponents,
229
354
  });
230
355
  }
231
356
  function resolveAgentAccountId(value) {
232
357
  const trimmed = value?.trim();
233
- if (!trimmed)
358
+ if (!trimmed) {
234
359
  return undefined;
360
+ }
235
361
  return normalizeAccountId(trimmed);
236
362
  }
237
363
  function filterActionsForContext(params) {
238
364
  const channel = normalizeMessageChannel(params.channel);
239
- if (!channel || channel !== "bluebubbles")
365
+ if (!channel || channel !== "bluebubbles") {
240
366
  return params.actions;
367
+ }
241
368
  const currentChannelId = params.currentChannelId?.trim();
242
- if (!currentChannelId)
369
+ if (!currentChannelId) {
243
370
  return params.actions;
371
+ }
244
372
  const normalizedTarget = normalizeTargetForProvider(channel, currentChannelId) ?? currentChannelId;
245
373
  const lowered = normalizedTarget.trim().toLowerCase();
246
374
  const isGroupTarget = lowered.startsWith("chat_guid:") ||
247
375
  lowered.startsWith("chat_id:") ||
248
376
  lowered.startsWith("chat_identifier:") ||
249
377
  lowered.startsWith("group:");
250
- if (isGroupTarget)
378
+ if (isGroupTarget) {
251
379
  return params.actions;
380
+ }
252
381
  return params.actions.filter((action) => !BLUEBUBBLES_GROUP_ACTIONS.has(action));
253
382
  }
254
383
  function buildMessageToolDescription(options) {
@@ -266,7 +395,7 @@ function buildMessageToolDescription(options) {
266
395
  if (channelActions.length > 0) {
267
396
  // Always include "send" as a base action
268
397
  const allActions = new Set(["send", ...channelActions]);
269
- const actionList = Array.from(allActions).sort().join(", ");
398
+ const actionList = Array.from(allActions).toSorted().join(", ");
270
399
  return `${baseDescription} Current channel (${options.currentChannel}) supports: ${actionList}.`;
271
400
  }
272
401
  }
@@ -281,7 +410,13 @@ function buildMessageToolDescription(options) {
281
410
  }
282
411
  export function createMessageTool(options) {
283
412
  const agentAccountId = resolveAgentAccountId(options?.agentAccountId);
284
- const schema = options?.config ? buildMessageToolSchema(options.config) : MessageToolSchema;
413
+ const schema = options?.config
414
+ ? buildMessageToolSchema({
415
+ cfg: options.config,
416
+ currentChannelProvider: options.currentChannelProvider,
417
+ currentChannelId: options.currentChannelId,
418
+ })
419
+ : MessageToolSchema;
285
420
  const description = buildMessageToolDescription({
286
421
  config: options?.config,
287
422
  currentChannel: options?.currentChannelProvider,
@@ -312,7 +447,6 @@ export function createMessageTool(options) {
312
447
  const action = readStringParam(params, "action", {
313
448
  required: true,
314
449
  });
315
- // Enforce explicit target when required (e.g. cron/hook-initiated runs).
316
450
  const requireExplicitTarget = options?.requireExplicitTarget === true;
317
451
  if (requireExplicitTarget && actionNeedsExplicitTarget(action)) {
318
452
  const explicitTarget = (typeof params.target === "string" && params.target.trim().length > 0) ||
@@ -328,10 +462,15 @@ export function createMessageTool(options) {
328
462
  if (accountId) {
329
463
  params.accountId = accountId;
330
464
  }
331
- const gateway = {
332
- url: readStringParam(params, "gatewayUrl", { trim: false }),
333
- token: readStringParam(params, "gatewayToken", { trim: false }),
465
+ const gatewayResolved = resolveGatewayOptions({
466
+ gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
467
+ gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
334
468
  timeoutMs: readNumberParam(params, "timeoutMs"),
469
+ });
470
+ const gateway = {
471
+ url: gatewayResolved.url,
472
+ token: gatewayResolved.token,
473
+ timeoutMs: gatewayResolved.timeoutMs,
335
474
  clientName: GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
336
475
  clientDisplayName: "agent",
337
476
  mode: GATEWAY_CLIENT_MODES.BACKEND,
@@ -359,6 +498,7 @@ export function createMessageTool(options) {
359
498
  defaultAccountId: accountId ?? undefined,
360
499
  gateway,
361
500
  toolContext,
501
+ sessionKey: options?.agentSessionKey,
362
502
  agentId: options?.agentSessionKey
363
503
  ? resolveSessionAgentId({ sessionKey: options.agentSessionKey, config: cfg })
364
504
  : undefined,
@@ -366,8 +506,9 @@ export function createMessageTool(options) {
366
506
  abortSignal: signal,
367
507
  });
368
508
  const toolResult = getToolResult(result);
369
- if (toolResult)
509
+ if (toolResult) {
370
510
  return toolResult;
511
+ }
371
512
  return jsonResult(result.payload);
372
513
  },
373
514
  };
@@ -1,15 +1,16 @@
1
1
  import crypto from "node:crypto";
2
2
  import { Type } from "@sinclair/typebox";
3
- import { cameraTempPath, parseCameraClipPayload, parseCameraSnapPayload, writeBase64ToFile, } from "../../cli/nodes-camera.js";
3
+ import { cameraTempPath, parseCameraClipPayload, parseCameraSnapPayload, writeBase64ToFile, writeUrlToFile, } from "../../cli/nodes-camera.js";
4
4
  import { parseEnvPairs, parseTimeoutMs } from "../../cli/nodes-run.js";
5
5
  import { parseScreenRecordPayload, screenRecordTempPath, writeScreenRecordToFile, } from "../../cli/nodes-screen.js";
6
6
  import { parseDurationMs } from "../../cli/parse-duration.js";
7
7
  import { imageMimeFromFormat } from "../../media/mime.js";
8
8
  import { resolveSessionAgentId } from "../agent-scope.js";
9
+ import { resolveImageSanitizationLimits } from "../image-sanitization.js";
9
10
  import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
10
11
  import { sanitizeToolResultImages } from "../tool-images.js";
11
12
  import { jsonResult, readStringParam } from "./common.js";
12
- import { callGatewayTool } from "./gateway.js";
13
+ import { callGatewayTool, readGatewayCallOptions } from "./gateway.js";
13
14
  import { listNodes, resolveNodeIdFromList, resolveNodeId } from "./nodes-utils.js";
14
15
  const NODES_TOOL_ACTIONS = [
15
16
  "status",
@@ -24,6 +25,7 @@ const NODES_TOOL_ACTIONS = [
24
25
  "screen_record",
25
26
  "location_get",
26
27
  "run",
28
+ "invoke",
27
29
  ];
28
30
  const NOTIFY_PRIORITIES = ["passive", "active", "timeSensitive"];
29
31
  const NOTIFY_DELIVERIES = ["system", "overlay", "auto"];
@@ -69,6 +71,9 @@ const NodesToolSchema = Type.Object({
69
71
  commandTimeoutMs: Type.Optional(Type.Number()),
70
72
  invokeTimeoutMs: Type.Optional(Type.Number()),
71
73
  needsScreenRecording: Type.Optional(Type.Boolean()),
74
+ // invoke
75
+ invokeCommand: Type.Optional(Type.String()),
76
+ invokeParamsJson: Type.Optional(Type.String()),
72
77
  });
73
78
  export function createNodesTool(options) {
74
79
  const sessionKey = options?.agentSessionKey?.trim() || undefined;
@@ -76,19 +81,16 @@ export function createNodesTool(options) {
76
81
  sessionKey: options?.agentSessionKey,
77
82
  config: options?.config,
78
83
  });
84
+ const imageSanitization = resolveImageSanitizationLimits(options?.config);
79
85
  return {
80
86
  label: "Nodes",
81
87
  name: "nodes",
82
- description: "Discover and control paired nodes (status/describe/pairing/notify/camera/screen/location/run).",
88
+ description: "Discover and control paired nodes (status/describe/pairing/notify/camera/screen/location/run/invoke).",
83
89
  parameters: NodesToolSchema,
84
90
  execute: async (_toolCallId, args) => {
85
91
  const params = args;
86
92
  const action = readStringParam(params, "action", { required: true });
87
- const gatewayOpts = {
88
- gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
89
- gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
90
- timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : undefined,
91
- };
93
+ const gatewayOpts = readGatewayCallOptions(params);
92
94
  try {
93
95
  switch (action) {
94
96
  case "status":
@@ -164,7 +166,7 @@ export function createNodesTool(options) {
164
166
  const content = [];
165
167
  const details = [];
166
168
  for (const facing of facings) {
167
- const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
169
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
168
170
  nodeId,
169
171
  command: "camera.snap",
170
172
  params: {
@@ -176,7 +178,7 @@ export function createNodesTool(options) {
176
178
  deviceId,
177
179
  },
178
180
  idempotencyKey: crypto.randomUUID(),
179
- }));
181
+ });
180
182
  const payload = parseCameraSnapPayload(raw?.payload);
181
183
  const normalizedFormat = payload.format.toLowerCase();
182
184
  if (normalizedFormat !== "jpg" &&
@@ -190,13 +192,20 @@ export function createNodesTool(options) {
190
192
  facing,
191
193
  ext: isJpeg ? "jpg" : "png",
192
194
  });
193
- await writeBase64ToFile(filePath, payload.base64);
195
+ if (payload.url) {
196
+ await writeUrlToFile(filePath, payload.url);
197
+ }
198
+ else if (payload.base64) {
199
+ await writeBase64ToFile(filePath, payload.base64);
200
+ }
194
201
  content.push({ type: "text", text: `MEDIA:${filePath}` });
195
- content.push({
196
- type: "image",
197
- data: payload.base64,
198
- mimeType: imageMimeFromFormat(payload.format) ?? (isJpeg ? "image/jpeg" : "image/png"),
199
- });
202
+ if (payload.base64) {
203
+ content.push({
204
+ type: "image",
205
+ data: payload.base64,
206
+ mimeType: imageMimeFromFormat(payload.format) ?? (isJpeg ? "image/jpeg" : "image/png"),
207
+ });
208
+ }
200
209
  details.push({
201
210
  facing,
202
211
  path: filePath,
@@ -205,17 +214,17 @@ export function createNodesTool(options) {
205
214
  });
206
215
  }
207
216
  const result = { content, details };
208
- return await sanitizeToolResultImages(result, "nodes:camera_snap");
217
+ return await sanitizeToolResultImages(result, "nodes:camera_snap", imageSanitization);
209
218
  }
210
219
  case "camera_list": {
211
220
  const node = readStringParam(params, "node", { required: true });
212
221
  const nodeId = await resolveNodeId(gatewayOpts, node);
213
- const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
222
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
214
223
  nodeId,
215
224
  command: "camera.list",
216
225
  params: {},
217
226
  idempotencyKey: crypto.randomUUID(),
218
- }));
227
+ });
219
228
  const payload = raw && typeof raw.payload === "object" && raw.payload !== null ? raw.payload : {};
220
229
  return jsonResult(payload);
221
230
  }
@@ -235,7 +244,7 @@ export function createNodesTool(options) {
235
244
  const deviceId = typeof params.deviceId === "string" && params.deviceId.trim()
236
245
  ? params.deviceId.trim()
237
246
  : undefined;
238
- const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
247
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
239
248
  nodeId,
240
249
  command: "camera.clip",
241
250
  params: {
@@ -246,14 +255,19 @@ export function createNodesTool(options) {
246
255
  deviceId,
247
256
  },
248
257
  idempotencyKey: crypto.randomUUID(),
249
- }));
258
+ });
250
259
  const payload = parseCameraClipPayload(raw?.payload);
251
260
  const filePath = cameraTempPath({
252
261
  kind: "clip",
253
262
  facing,
254
263
  ext: payload.format,
255
264
  });
256
- await writeBase64ToFile(filePath, payload.base64);
265
+ if (payload.url) {
266
+ await writeUrlToFile(filePath, payload.url);
267
+ }
268
+ else if (payload.base64) {
269
+ await writeBase64ToFile(filePath, payload.base64);
270
+ }
257
271
  return {
258
272
  content: [{ type: "text", text: `FILE:${filePath}` }],
259
273
  details: {
@@ -277,7 +291,7 @@ export function createNodesTool(options) {
277
291
  ? params.screenIndex
278
292
  : 0;
279
293
  const includeAudio = typeof params.includeAudio === "boolean" ? params.includeAudio : true;
280
- const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
294
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
281
295
  nodeId,
282
296
  command: "screen.record",
283
297
  params: {
@@ -288,7 +302,7 @@ export function createNodesTool(options) {
288
302
  includeAudio,
289
303
  },
290
304
  idempotencyKey: crypto.randomUUID(),
291
- }));
305
+ });
292
306
  const payload = parseScreenRecordPayload(raw?.payload);
293
307
  const filePath = typeof params.outPath === "string" && params.outPath.trim()
294
308
  ? params.outPath.trim()
@@ -320,7 +334,7 @@ export function createNodesTool(options) {
320
334
  Number.isFinite(params.locationTimeoutMs)
321
335
  ? params.locationTimeoutMs
322
336
  : undefined;
323
- const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
337
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
324
338
  nodeId,
325
339
  command: "location.get",
326
340
  params: {
@@ -329,7 +343,7 @@ export function createNodesTool(options) {
329
343
  timeoutMs: locationTimeoutMs,
330
344
  },
331
345
  idempotencyKey: crypto.randomUUID(),
332
- }));
346
+ });
333
347
  return jsonResult(raw?.payload ?? {});
334
348
  }
335
349
  case "run": {
@@ -364,23 +378,101 @@ export function createNodesTool(options) {
364
378
  const needsScreenRecording = typeof params.needsScreenRecording === "boolean"
365
379
  ? params.needsScreenRecording
366
380
  : undefined;
367
- const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
381
+ const runParams = {
382
+ command,
383
+ cwd,
384
+ env,
385
+ timeoutMs: commandTimeoutMs,
386
+ needsScreenRecording,
387
+ agentId,
388
+ sessionKey,
389
+ };
390
+ // First attempt without approval flags.
391
+ try {
392
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
393
+ nodeId,
394
+ command: "system.run",
395
+ params: runParams,
396
+ timeoutMs: invokeTimeoutMs,
397
+ idempotencyKey: crypto.randomUUID(),
398
+ });
399
+ return jsonResult(raw?.payload ?? {});
400
+ }
401
+ catch (firstErr) {
402
+ const msg = firstErr instanceof Error ? firstErr.message : String(firstErr);
403
+ if (!msg.includes("SYSTEM_RUN_DENIED: approval required")) {
404
+ throw firstErr;
405
+ }
406
+ }
407
+ // Node requires approval – create a pending approval request on
408
+ // the gateway and wait for the user to approve/deny via the UI.
409
+ const APPROVAL_TIMEOUT_MS = 120_000;
410
+ const cmdText = command.join(" ");
411
+ const approvalId = crypto.randomUUID();
412
+ const approvalResult = await callGatewayTool("exec.approval.request", { ...gatewayOpts, timeoutMs: APPROVAL_TIMEOUT_MS + 5_000 }, {
413
+ id: approvalId,
414
+ command: cmdText,
415
+ cwd,
416
+ host: "node",
417
+ agentId,
418
+ sessionKey,
419
+ timeoutMs: APPROVAL_TIMEOUT_MS,
420
+ });
421
+ const decisionRaw = approvalResult && typeof approvalResult === "object"
422
+ ? approvalResult.decision
423
+ : undefined;
424
+ const approvalDecision = decisionRaw === "allow-once" || decisionRaw === "allow-always" ? decisionRaw : null;
425
+ if (!approvalDecision) {
426
+ if (decisionRaw === "deny") {
427
+ throw new Error("exec denied: user denied");
428
+ }
429
+ if (decisionRaw === undefined || decisionRaw === null) {
430
+ throw new Error("exec denied: approval timed out");
431
+ }
432
+ throw new Error("exec denied: invalid approval decision");
433
+ }
434
+ // Retry with the approval decision.
435
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
368
436
  nodeId,
369
437
  command: "system.run",
370
438
  params: {
371
- command,
372
- cwd,
373
- env,
374
- timeoutMs: commandTimeoutMs,
375
- needsScreenRecording,
376
- agentId,
377
- sessionKey,
439
+ ...runParams,
440
+ runId: approvalId,
441
+ approved: true,
442
+ approvalDecision,
378
443
  },
379
444
  timeoutMs: invokeTimeoutMs,
380
445
  idempotencyKey: crypto.randomUUID(),
381
- }));
446
+ });
382
447
  return jsonResult(raw?.payload ?? {});
383
448
  }
449
+ case "invoke": {
450
+ const node = readStringParam(params, "node", { required: true });
451
+ const nodeId = await resolveNodeId(gatewayOpts, node);
452
+ const invokeCommand = readStringParam(params, "invokeCommand", { required: true });
453
+ const invokeParamsJson = typeof params.invokeParamsJson === "string" ? params.invokeParamsJson.trim() : "";
454
+ let invokeParams = {};
455
+ if (invokeParamsJson) {
456
+ try {
457
+ invokeParams = JSON.parse(invokeParamsJson);
458
+ }
459
+ catch (err) {
460
+ const message = err instanceof Error ? err.message : String(err);
461
+ throw new Error(`invokeParamsJson must be valid JSON: ${message}`, {
462
+ cause: err,
463
+ });
464
+ }
465
+ }
466
+ const invokeTimeoutMs = parseTimeoutMs(params.invokeTimeoutMs);
467
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
468
+ nodeId,
469
+ command: invokeCommand,
470
+ params: invokeParams,
471
+ timeoutMs: invokeTimeoutMs,
472
+ idempotencyKey: crypto.randomUUID(),
473
+ });
474
+ return jsonResult(raw ?? {});
475
+ }
384
476
  default:
385
477
  throw new Error(`Unknown action: ${action}`);
386
478
  }
@@ -392,7 +484,7 @@ export function createNodesTool(options) {
392
484
  : "default";
393
485
  const agentLabel = agentId ?? "unknown";
394
486
  const message = err instanceof Error ? err.message : String(err);
395
- throw new Error(`agent=${agentLabel} node=${nodeLabel} gateway=${gatewayLabel} action=${action}: ${message}`);
487
+ throw new Error(`agent=${agentLabel} node=${nodeLabel} gateway=${gatewayLabel} action=${action}: ${message}`, { cause: err });
396
488
  }
397
489
  },
398
490
  };