@poolzin/pool-bot 2026.2.25 → 2026.2.26

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 (506) hide show
  1. package/dist/acp/event-mapper.js +87 -22
  2. package/dist/acp/meta.js +12 -6
  3. package/dist/agents/agent-paths.js +8 -9
  4. package/dist/agents/agent-scope.js +7 -5
  5. package/dist/agents/auth-profiles/oauth.js +148 -64
  6. package/dist/agents/auth-profiles/session-override.js +13 -7
  7. package/dist/agents/bash-tools.exec-host-gateway.js +14 -4
  8. package/dist/agents/bash-tools.exec-runtime.js +2 -25
  9. package/dist/agents/bedrock-discovery.js +3 -1
  10. package/dist/agents/byteplus-models.js +97 -0
  11. package/dist/agents/chutes-oauth.js +1 -0
  12. package/dist/agents/cli-runner/helpers.js +4 -0
  13. package/dist/agents/compaction.js +41 -14
  14. package/dist/agents/doubao-models.js +121 -0
  15. package/dist/agents/failover-error.js +2 -0
  16. package/dist/agents/huggingface-models.js +5 -3
  17. package/dist/agents/live-model-filter.js +5 -0
  18. package/dist/agents/minimax-vlm.js +10 -8
  19. package/dist/agents/model-auth.js +6 -0
  20. package/dist/agents/model-catalog.js +3 -1
  21. package/dist/agents/model-selection.js +7 -1
  22. package/dist/agents/models-config.providers.js +93 -11
  23. package/dist/agents/ollama-stream.js +117 -4
  24. package/dist/agents/opencode-zen-models.js +22 -11
  25. package/dist/agents/pi-embedded-helpers/errors.js +55 -33
  26. package/dist/agents/pi-embedded-helpers/messaging-dedupe.js +10 -5
  27. package/dist/agents/pi-embedded-helpers/thinking.js +10 -5
  28. package/dist/agents/pi-embedded-helpers.js +1 -1
  29. package/dist/agents/pi-embedded-runner/compact.js +29 -7
  30. package/dist/agents/pi-embedded-runner/extensions.js +28 -26
  31. package/dist/agents/pi-embedded-runner/google.js +20 -8
  32. package/dist/agents/pi-embedded-runner/run/attempt.js +95 -36
  33. package/dist/agents/pi-embedded-runner/run.js +71 -12
  34. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +11 -2
  35. package/dist/agents/pi-embedded-runner/session-manager-cache.js +11 -7
  36. package/dist/agents/pi-embedded-runner/system-prompt.js +2 -0
  37. package/dist/agents/pi-embedded-runner/thinking.js +42 -0
  38. package/dist/agents/pi-embedded-runner/tool-name-allowlist.js +19 -0
  39. package/dist/agents/pi-embedded-runner/utils.js +7 -10
  40. package/dist/agents/pi-embedded-subscribe.handlers.lifecycle.js +45 -56
  41. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +2 -2
  42. package/dist/agents/pi-embedded-subscribe.js +9 -4
  43. package/dist/agents/pi-embedded-subscribe.tools.js +68 -14
  44. package/dist/agents/pi-embedded-utils.js +3 -0
  45. package/dist/agents/pi-extensions/compaction-safeguard-runtime.js +4 -20
  46. package/dist/agents/pi-extensions/compaction-safeguard.js +75 -33
  47. package/dist/agents/pi-settings.js +40 -0
  48. package/dist/agents/pi-tools.policy.js +2 -1
  49. package/dist/agents/provider/config-loader.js +1 -1
  50. package/dist/agents/sandbox/browser.js +170 -33
  51. package/dist/agents/sandbox/config-hash.js +14 -27
  52. package/dist/agents/sandbox/config.js +21 -2
  53. package/dist/agents/sandbox/constants.js +2 -0
  54. package/dist/agents/sandbox/docker.js +16 -2
  55. package/dist/agents/sandbox/novnc-auth.js +62 -0
  56. package/dist/agents/sandbox/sanitize-env-vars.js +1 -1
  57. package/dist/agents/sandbox/shared.js +10 -6
  58. package/dist/agents/sandbox-paths.js +24 -11
  59. package/dist/agents/schema/clean-for-gemini.js +132 -85
  60. package/dist/agents/session-slug.js +10 -5
  61. package/dist/agents/session-tool-result-guard-wrapper.js +1 -0
  62. package/dist/agents/session-tool-result-guard.js +3 -1
  63. package/dist/agents/session-transcript-repair.js +40 -6
  64. package/dist/agents/skills/bundled-dir.js +19 -5
  65. package/dist/agents/skills/env-overrides.js +124 -43
  66. package/dist/agents/skills/frontmatter.js +6 -6
  67. package/dist/agents/skills/plugin-skills.js +14 -7
  68. package/dist/agents/skills/workspace.js +1 -0
  69. package/dist/agents/subagent-announce.js +251 -49
  70. package/dist/agents/subagent-lifecycle-events.js +19 -0
  71. package/dist/agents/subagent-registry-cleanup.js +31 -0
  72. package/dist/agents/subagent-registry-completion.js +68 -0
  73. package/dist/agents/subagent-registry-queries.js +117 -0
  74. package/dist/agents/subagent-registry-state.js +46 -0
  75. package/dist/agents/subagent-registry.js +252 -221
  76. package/dist/agents/subagent-registry.store.js +1 -0
  77. package/dist/agents/subagent-registry.types.js +1 -0
  78. package/dist/agents/subagent-spawn.js +195 -7
  79. package/dist/agents/system-prompt.js +22 -6
  80. package/dist/agents/test-helpers/fast-coding-tools.js +1 -18
  81. package/dist/agents/test-helpers/fast-core-tools.js +1 -17
  82. package/dist/agents/timeout.js +18 -6
  83. package/dist/agents/tool-call-id.js +1 -1
  84. package/dist/agents/tool-display-common.js +162 -29
  85. package/dist/agents/tool-images.js +82 -9
  86. package/dist/agents/tool-policy.js +51 -26
  87. package/dist/agents/tools/browser-tool.js +2 -2
  88. package/dist/agents/tools/canvas-tool.js +27 -1
  89. package/dist/agents/tools/common.js +45 -0
  90. package/dist/agents/tools/discord-actions-guild.js +4 -1
  91. package/dist/agents/tools/gateway-tool.js +3 -1
  92. package/dist/agents/tools/nodes-utils.js +1 -10
  93. package/dist/agents/tools/sessions-send-helpers.js +12 -6
  94. package/dist/agents/tools/sessions-spawn-tool.js +8 -2
  95. package/dist/agents/tools/subagents-tool.js +2 -1
  96. package/dist/agents/tools/whatsapp-actions.js +10 -2
  97. package/dist/agents/tools/whatsapp-target-auth.js +18 -0
  98. package/dist/agents/transcript-policy.js +22 -8
  99. package/dist/agents/venice-models.js +11 -3
  100. package/dist/auto-reply/commands-registry.data.js +51 -0
  101. package/dist/auto-reply/commands-registry.js +4 -3
  102. package/dist/auto-reply/group-activation.js +10 -5
  103. package/dist/auto-reply/inbound-debounce.js +10 -5
  104. package/dist/auto-reply/reply/abort.js +1 -1
  105. package/dist/auto-reply/reply/agent-runner-execution.js +4 -1
  106. package/dist/auto-reply/reply/bash-command.js +41 -39
  107. package/dist/auto-reply/reply/command-gates.js +25 -0
  108. package/dist/auto-reply/reply/commands-allowlist.js +111 -72
  109. package/dist/auto-reply/reply/commands-bash.js +6 -5
  110. package/dist/auto-reply/reply/commands-config.js +30 -28
  111. package/dist/auto-reply/reply/commands-core.js +2 -1
  112. package/dist/auto-reply/reply/commands-info.js +1 -0
  113. package/dist/auto-reply/reply/commands-models.js +65 -14
  114. package/dist/auto-reply/reply/commands-session.js +237 -82
  115. package/dist/auto-reply/reply/commands-setunset.js +45 -0
  116. package/dist/auto-reply/reply/commands-subagents/action-agents.js +44 -0
  117. package/dist/auto-reply/reply/commands-subagents/action-focus.js +64 -0
  118. package/dist/auto-reply/reply/commands-subagents/action-help.js +4 -0
  119. package/dist/auto-reply/reply/commands-subagents/action-info.js +45 -0
  120. package/dist/auto-reply/reply/commands-subagents/action-kill.js +60 -0
  121. package/dist/auto-reply/reply/commands-subagents/action-list.js +44 -0
  122. package/dist/auto-reply/reply/commands-subagents/action-log.js +29 -0
  123. package/dist/auto-reply/reply/commands-subagents/action-send.js +119 -0
  124. package/dist/auto-reply/reply/commands-subagents/action-spawn.js +52 -0
  125. package/dist/auto-reply/reply/commands-subagents/action-unfocus.js +30 -0
  126. package/dist/auto-reply/reply/commands-subagents/shared.js +303 -0
  127. package/dist/auto-reply/reply/commands-subagents.js +51 -587
  128. package/dist/auto-reply/reply/commands-tts.js +10 -5
  129. package/dist/auto-reply/reply/config-value.js +10 -5
  130. package/dist/auto-reply/reply/directive-handling.model-picker.js +12 -6
  131. package/dist/auto-reply/reply/directive-handling.persist.js +9 -21
  132. package/dist/auto-reply/reply/directive-handling.shared.js +24 -4
  133. package/dist/auto-reply/reply/followup-runner.js +1 -0
  134. package/dist/auto-reply/reply/get-reply-directives-utils.js +23 -14
  135. package/dist/auto-reply/reply/get-reply-directives.js +17 -28
  136. package/dist/auto-reply/reply/get-reply-inline-actions.js +1 -0
  137. package/dist/auto-reply/reply/get-reply.js +71 -12
  138. package/dist/auto-reply/reply/model-selection.js +80 -39
  139. package/dist/auto-reply/reply/queue/enqueue.js +10 -5
  140. package/dist/auto-reply/reply/queue/state.js +13 -12
  141. package/dist/auto-reply/reply/reply-payloads.js +67 -36
  142. package/dist/auto-reply/reply/reply-reference.js +9 -8
  143. package/dist/auto-reply/reply/route-reply.js +15 -8
  144. package/dist/auto-reply/reply/session-reset-prompt.js +1 -1
  145. package/dist/auto-reply/reply/session.js +22 -6
  146. package/dist/auto-reply/reply/strip-inbound-meta.js +147 -0
  147. package/dist/auto-reply/reply/subagents-utils.js +56 -30
  148. package/dist/auto-reply/reply/typing.js +46 -21
  149. package/dist/auto-reply/send-policy.js +14 -7
  150. package/dist/auto-reply/status.js +140 -16
  151. package/dist/auto-reply/templating.js +10 -5
  152. package/dist/auto-reply/thinking.js +7 -16
  153. package/dist/auto-reply/tokens.js +21 -5
  154. package/dist/browser/bridge-server.js +36 -20
  155. package/dist/browser/cdp.helpers.js +7 -14
  156. package/dist/browser/cdp.js +35 -15
  157. package/dist/browser/chrome.profile-decoration.js +7 -4
  158. package/dist/browser/config.js +4 -0
  159. package/dist/browser/extension-relay-auth.js +55 -0
  160. package/dist/browser/extension-relay.js +74 -29
  161. package/dist/browser/navigation-guard.js +9 -1
  162. package/dist/browser/paths.js +77 -0
  163. package/dist/browser/profiles.js +13 -8
  164. package/dist/browser/pw-ai-module.js +10 -5
  165. package/dist/browser/pw-session.js +76 -39
  166. package/dist/browser/pw-tools-core.interactions.js +14 -7
  167. package/dist/browser/pw-tools-core.state.js +12 -6
  168. package/dist/browser/routes/agent.act.js +2 -2
  169. package/dist/browser/server-context.js +7 -0
  170. package/dist/build-info.json +3 -3
  171. package/dist/channels/allow-from.js +2 -1
  172. package/dist/channels/allowlists/resolve-utils.js +43 -19
  173. package/dist/channels/channel-config.js +14 -7
  174. package/dist/channels/draft-stream-loop.js +7 -0
  175. package/dist/channels/model-overrides.js +82 -0
  176. package/dist/channels/plugins/normalize/imessage.js +14 -7
  177. package/dist/channels/plugins/normalize/slack.js +10 -5
  178. package/dist/channels/plugins/normalize/telegram.js +14 -7
  179. package/dist/channels/plugins/outbound/discord.js +80 -8
  180. package/dist/channels/plugins/outbound/signal.js +11 -11
  181. package/dist/channels/plugins/setup-helpers.js +10 -5
  182. package/dist/channels/sender-label.js +14 -7
  183. package/dist/channels/session.js +4 -2
  184. package/dist/channels/status-reactions.js +297 -0
  185. package/dist/cli/banner.js +1 -1
  186. package/dist/cli/browser-cli-actions-input/register.files-downloads.js +65 -56
  187. package/dist/cli/cli-name.js +11 -11
  188. package/dist/cli/cli-utils.js +13 -3
  189. package/dist/cli/command-format.js +1 -1
  190. package/dist/cli/config-cli.js +1 -1
  191. package/dist/cli/daemon-cli/lifecycle-core.js +31 -19
  192. package/dist/cli/daemon-cli/lifecycle.js +64 -2
  193. package/dist/cli/daemon-cli/restart-health.js +126 -0
  194. package/dist/cli/daemon-cli/status.gather.js +9 -13
  195. package/dist/cli/daemon-cli/status.print.js +2 -10
  196. package/dist/cli/deps.js +27 -22
  197. package/dist/cli/gateway-cli/run-loop.js +23 -5
  198. package/dist/cli/node-cli/register.js +14 -5
  199. package/dist/cli/nodes-media-utils.js +7 -2
  200. package/dist/cli/outbound-send-deps.js +2 -9
  201. package/dist/cli/outbound-send-mapping.js +11 -0
  202. package/dist/cli/pairing-cli.js +40 -14
  203. package/dist/cli/plugins-cli.js +34 -41
  204. package/dist/cli/ports.js +11 -10
  205. package/dist/cli/program/command-registry.js +2 -11
  206. package/dist/cli/program/command-tree.js +16 -0
  207. package/dist/cli/program/preaction.js +13 -9
  208. package/dist/cli/program/register.configure.js +3 -18
  209. package/dist/cli/program/register.maintenance.js +2 -2
  210. package/dist/cli/program/register.onboard.js +2 -0
  211. package/dist/cli/program/register.status-health-sessions.js +16 -17
  212. package/dist/cli/program/register.subclis.js +93 -52
  213. package/dist/cli/route.js +11 -7
  214. package/dist/cli/system-cli.js +36 -46
  215. package/dist/cli/update-cli/shared.js +22 -9
  216. package/dist/cli/update-cli/update-command.js +89 -14
  217. package/dist/cli/update-cli/wizard.js +6 -12
  218. package/dist/commands/agent/run-context.js +18 -5
  219. package/dist/commands/agent/session-store.js +17 -4
  220. package/dist/commands/agent.js +22 -2
  221. package/dist/commands/agents.bindings.js +14 -7
  222. package/dist/commands/agents.commands.add.js +13 -9
  223. package/dist/commands/agents.commands.identity.js +12 -6
  224. package/dist/commands/agents.commands.list.js +11 -6
  225. package/dist/commands/agents.config.js +8 -10
  226. package/dist/commands/agents.providers.js +12 -6
  227. package/dist/commands/auth-choice-options.js +103 -75
  228. package/dist/commands/auth-choice.apply.byteplus.js +55 -0
  229. package/dist/commands/auth-choice.apply.js +4 -0
  230. package/dist/commands/auth-choice.apply.minimax.js +61 -13
  231. package/dist/commands/auth-choice.apply.openai.js +3 -1
  232. package/dist/commands/auth-choice.apply.volcengine.js +55 -0
  233. package/dist/commands/auth-choice.preferred-provider.js +2 -0
  234. package/dist/commands/channels/remove.js +13 -6
  235. package/dist/commands/channels/shared.js +4 -14
  236. package/dist/commands/configure.commands.js +14 -0
  237. package/dist/commands/configure.gateway.js +2 -4
  238. package/dist/commands/configure.js +1 -1
  239. package/dist/commands/configure.shared.js +11 -0
  240. package/dist/commands/daemon-install-helpers.js +2 -2
  241. package/dist/commands/dashboard.js +12 -10
  242. package/dist/commands/docs.js +14 -8
  243. package/dist/commands/doctor-config-flow.js +11 -9
  244. package/dist/commands/doctor-legacy-config.js +281 -0
  245. package/dist/commands/doctor-state-integrity.js +99 -23
  246. package/dist/commands/doctor-update.js +12 -9
  247. package/dist/commands/models/list.list-command.js +7 -5
  248. package/dist/commands/models/set-image.js +2 -21
  249. package/dist/commands/node-daemon-install-helpers.js +10 -8
  250. package/dist/commands/onboard-auth.config-minimax.js +54 -80
  251. package/dist/commands/onboard-auth.config-opencode.js +2 -18
  252. package/dist/commands/onboard-auth.credentials.js +90 -13
  253. package/dist/commands/onboard-auth.js +1 -1
  254. package/dist/commands/onboard-auth.models.js +6 -5
  255. package/dist/commands/onboard-hooks.js +1 -1
  256. package/dist/commands/onboard-non-interactive/api-keys.js +14 -7
  257. package/dist/commands/onboard-non-interactive/local/auth-choice.js +64 -49
  258. package/dist/commands/onboard-provider-auth-flags.js +14 -0
  259. package/dist/commands/onboard-remote.js +14 -7
  260. package/dist/commands/onboard.js +11 -13
  261. package/dist/commands/sandbox-display.js +6 -5
  262. package/dist/commands/status-all/diagnosis.js +14 -10
  263. package/dist/commands/status-all/format.js +1 -0
  264. package/dist/commands/status.gateway-probe.js +1 -16
  265. package/dist/commands/systemd-linger.js +12 -6
  266. package/dist/config/agent-limits.js +2 -0
  267. package/dist/config/commands.js +30 -16
  268. package/dist/config/config-paths.js +9 -11
  269. package/dist/config/defaults.js +22 -2
  270. package/dist/config/discord-preview-streaming.js +104 -0
  271. package/dist/config/env-vars.js +37 -8
  272. package/dist/config/includes.js +4 -0
  273. package/dist/config/io.js +97 -12
  274. package/dist/config/legacy.migrations.part-1.js +189 -78
  275. package/dist/config/legacy.shared.js +3 -1
  276. package/dist/config/merge-patch.js +4 -0
  277. package/dist/config/prototype-keys.js +4 -0
  278. package/dist/config/schema.help.js +44 -7
  279. package/dist/config/schema.labels.js +38 -6
  280. package/dist/config/sessions/delivery-info.js +10 -3
  281. package/dist/config/sessions/main-session.js +10 -5
  282. package/dist/config/sessions/session-file.js +33 -0
  283. package/dist/config/sessions/session-key.js +10 -5
  284. package/dist/config/sessions/store.js +1 -1
  285. package/dist/config/sessions.js +1 -0
  286. package/dist/config/zod-schema.agent-runtime.js +11 -0
  287. package/dist/config/zod-schema.js +148 -13
  288. package/dist/config/zod-schema.providers-core.js +78 -4
  289. package/dist/config/zod-schema.providers.js +6 -1
  290. package/dist/config/zod-schema.session.js +41 -2
  291. package/dist/cron/run-log.js +3 -0
  292. package/dist/cron/schedule.js +21 -10
  293. package/dist/cron/service/ops.js +35 -21
  294. package/dist/cron/service/timer.js +116 -16
  295. package/dist/cron/stagger.js +3 -1
  296. package/dist/discord/api.js +12 -6
  297. package/dist/discord/draft-chunking.js +22 -0
  298. package/dist/discord/draft-stream.js +124 -0
  299. package/dist/discord/monitor/agent-components.js +1 -1
  300. package/dist/discord/monitor/commands.js +5 -0
  301. package/dist/discord/monitor/gateway-plugin.js +2 -1
  302. package/dist/discord/monitor/listeners.js +37 -27
  303. package/dist/discord/monitor/message-handler.js +4 -1
  304. package/dist/discord/monitor/message-handler.preflight.js +65 -8
  305. package/dist/discord/monitor/message-handler.process.js +246 -217
  306. package/dist/discord/monitor/message-utils.js +143 -6
  307. package/dist/discord/monitor/model-picker-preferences.js +143 -0
  308. package/dist/discord/monitor/model-picker.js +651 -0
  309. package/dist/discord/monitor/native-command.js +573 -16
  310. package/dist/discord/monitor/provider.allowlist.js +223 -0
  311. package/dist/discord/monitor/provider.js +275 -347
  312. package/dist/discord/monitor/provider.lifecycle.js +100 -0
  313. package/dist/discord/monitor/reply-delivery.js +123 -16
  314. package/dist/discord/monitor/thread-bindings.discord-api.js +215 -0
  315. package/dist/discord/monitor/thread-bindings.js +4 -0
  316. package/dist/discord/monitor/thread-bindings.lifecycle.js +177 -0
  317. package/dist/discord/monitor/thread-bindings.manager.js +423 -0
  318. package/dist/discord/monitor/thread-bindings.messages.js +55 -0
  319. package/dist/discord/monitor/thread-bindings.state.js +358 -0
  320. package/dist/discord/monitor/thread-bindings.types.js +6 -0
  321. package/dist/discord/resolve-users.js +33 -21
  322. package/dist/discord/send.channels.js +15 -0
  323. package/dist/discord/send.js +3 -2
  324. package/dist/discord/send.outbound.js +82 -26
  325. package/dist/discord/send.permissions.js +83 -30
  326. package/dist/discord/send.reactions.js +8 -4
  327. package/dist/discord/token.js +10 -5
  328. package/dist/discord/voice/command.js +263 -0
  329. package/dist/discord/voice/manager.js +531 -0
  330. package/dist/gateway/auth.js +34 -10
  331. package/dist/gateway/call.js +4 -16
  332. package/dist/gateway/client.js +28 -4
  333. package/dist/gateway/config-reload.js +3 -4
  334. package/dist/gateway/control-ui.js +219 -96
  335. package/dist/gateway/hooks-mapping.js +88 -38
  336. package/dist/gateway/http-auth-helpers.js +3 -2
  337. package/dist/gateway/http-endpoint-helpers.js +1 -0
  338. package/dist/gateway/net.js +54 -12
  339. package/dist/gateway/node-invoke-system-run-approval.js +14 -35
  340. package/dist/gateway/node-registry.js +10 -5
  341. package/dist/gateway/openai-http.js +1 -0
  342. package/dist/gateway/openresponses-http.js +1 -0
  343. package/dist/gateway/origin-check.js +1 -18
  344. package/dist/gateway/protocol/index.js +4 -3
  345. package/dist/gateway/protocol/schema/cron.js +1 -0
  346. package/dist/gateway/protocol/schema/devices.js +1 -0
  347. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -1
  348. package/dist/gateway/protocol/schema/sessions.js +6 -0
  349. package/dist/gateway/role-policy.js +17 -0
  350. package/dist/gateway/server/ws-connection/connect-policy.js +37 -0
  351. package/dist/gateway/server/ws-connection/message-handler.js +175 -148
  352. package/dist/gateway/server-chat.js +83 -25
  353. package/dist/gateway/server-constants.js +10 -9
  354. package/dist/gateway/server-cron.js +1 -0
  355. package/dist/gateway/server-http.js +16 -7
  356. package/dist/gateway/server-maintenance.js +20 -5
  357. package/dist/gateway/server-methods/chat.js +10 -6
  358. package/dist/gateway/server-methods/config.js +12 -14
  359. package/dist/gateway/server-methods/devices.js +17 -3
  360. package/dist/gateway/server-methods/models.js +11 -1
  361. package/dist/gateway/server-methods/sessions.js +64 -8
  362. package/dist/gateway/server-methods/usage.js +162 -75
  363. package/dist/gateway/server-node-events.js +29 -0
  364. package/dist/gateway/server-runtime-config.js +34 -13
  365. package/dist/gateway/server-startup-memory.js +17 -11
  366. package/dist/gateway/session-utils.fs.js +32 -34
  367. package/dist/gateway/sessions-resolve.js +17 -5
  368. package/dist/gateway/test-helpers.openai-mock.js +14 -7
  369. package/dist/gateway/tools-invoke-http.js +21 -10
  370. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +3 -1
  371. package/dist/hooks/bundled/command-logger/handler.js +7 -2
  372. package/dist/hooks/bundled/session-memory/handler.js +6 -5
  373. package/dist/hooks/frontmatter.js +6 -6
  374. package/dist/hooks/gmail-watcher.js +11 -6
  375. package/dist/hooks/internal-hooks.js +11 -1
  376. package/dist/hooks/llm-slug-generator.js +4 -1
  377. package/dist/hooks/workspace.js +47 -17
  378. package/dist/imessage/accounts.js +9 -20
  379. package/dist/imessage/monitor/inbound-processing.js +2 -1
  380. package/dist/infra/archive.js +174 -73
  381. package/dist/infra/control-ui-assets.js +14 -6
  382. package/dist/infra/device-pairing.js +108 -29
  383. package/dist/infra/env.js +10 -5
  384. package/dist/infra/exec-approvals-allowlist.js +122 -0
  385. package/dist/infra/exec-approvals-analysis.js +34 -3
  386. package/dist/infra/exec-approvals.js +5 -17
  387. package/dist/infra/exec-safe-bin-policy.js +53 -45
  388. package/dist/infra/fs-safe.js +71 -39
  389. package/dist/infra/gateway-lock.js +6 -2
  390. package/dist/infra/heartbeat-wake.js +6 -12
  391. package/dist/infra/host-env-security-policy.json +19 -0
  392. package/dist/infra/host-env-security.js +66 -0
  393. package/dist/infra/net/ssrf.js +131 -38
  394. package/dist/infra/outbound/bound-delivery-router.js +88 -0
  395. package/dist/infra/outbound/channel-selection.js +12 -6
  396. package/dist/infra/outbound/envelope.js +1 -1
  397. package/dist/infra/outbound/format.js +12 -6
  398. package/dist/infra/outbound/payloads.js +14 -7
  399. package/dist/infra/outbound/session-binding-service.js +123 -0
  400. package/dist/infra/path-guards.js +25 -0
  401. package/dist/infra/provider-usage.fetch.codex.js +7 -15
  402. package/dist/infra/provider-usage.fetch.gemini.js +14 -11
  403. package/dist/infra/provider-usage.fetch.shared.js +30 -1
  404. package/dist/infra/provider-usage.fetch.zai.js +10 -9
  405. package/dist/infra/retry-policy.js +4 -2
  406. package/dist/infra/retry.js +9 -5
  407. package/dist/infra/session-cost-usage.js +107 -59
  408. package/dist/infra/session-maintenance-warning.js +3 -1
  409. package/dist/infra/shell-env.js +98 -34
  410. package/dist/infra/ssh-config.js +12 -6
  411. package/dist/infra/system-run-command.js +49 -4
  412. package/dist/infra/update-channels.js +10 -5
  413. package/dist/line/accounts.js +5 -7
  414. package/dist/line/bot-access.js +8 -20
  415. package/dist/line/bot-handlers.js +3 -1
  416. package/dist/link-understanding/detect.js +15 -7
  417. package/dist/media/constants.js +15 -6
  418. package/dist/media/image-ops.js +7 -0
  419. package/dist/media/local-roots.js +3 -2
  420. package/dist/media-understanding/apply.js +4 -1
  421. package/dist/media-understanding/concurrency.js +8 -20
  422. package/dist/memory/backend-config.js +45 -6
  423. package/dist/memory/embeddings.js +10 -4
  424. package/dist/memory/fs-utils.js +23 -0
  425. package/dist/memory/manager-search.js +12 -6
  426. package/dist/memory/manager-sync-ops.js +12 -2
  427. package/dist/memory/qmd-manager.js +466 -53
  428. package/dist/memory/query-expansion.js +167 -3
  429. package/dist/memory/status-format.js +10 -5
  430. package/dist/memory/sync-memory-files.js +1 -1
  431. package/dist/node-host/invoke-system-run.js +281 -0
  432. package/dist/node-host/invoke.js +55 -337
  433. package/dist/pairing/pairing-store.js +22 -0
  434. package/dist/plugin-sdk/allow-from.js +1 -1
  435. package/dist/plugin-sdk/command-auth.js +3 -1
  436. package/dist/plugin-sdk/index.js +6 -3
  437. package/dist/plugin-sdk/webhook-targets.js +32 -0
  438. package/dist/plugins/bundled-dir.js +9 -6
  439. package/dist/plugins/hooks.js +50 -0
  440. package/dist/plugins/install.js +28 -16
  441. package/dist/plugins/runtime.js +3 -17
  442. package/dist/plugins/update.js +78 -12
  443. package/dist/process/spawn-utils.js +14 -7
  444. package/dist/providers/github-copilot-token.js +11 -6
  445. package/dist/providers/qwen-portal-oauth.js +14 -6
  446. package/dist/routing/account-id.js +30 -0
  447. package/dist/routing/resolve-route.js +3 -7
  448. package/dist/routing/session-key.js +2 -16
  449. package/dist/security/audit-channel.js +93 -2
  450. package/dist/security/audit-extra.async.js +159 -5
  451. package/dist/security/audit-extra.js +1 -1
  452. package/dist/security/audit-extra.sync.js +85 -6
  453. package/dist/security/audit.js +40 -4
  454. package/dist/security/dm-policy-shared.js +44 -0
  455. package/dist/security/external-content.js +26 -6
  456. package/dist/shared/entry-status.js +6 -0
  457. package/dist/shared/frontmatter.js +5 -5
  458. package/dist/shared/node-match.js +11 -4
  459. package/dist/shared/operator-scope-compat.js +8 -3
  460. package/dist/signal/accounts.js +7 -20
  461. package/dist/signal/monitor/event-handler.js +3 -1
  462. package/dist/slack/accounts.js +6 -19
  463. package/dist/slack/actions.js +11 -3
  464. package/dist/slack/monitor/auth.js +1 -1
  465. package/dist/slack/monitor/message-handler/dispatch.js +50 -29
  466. package/dist/slack/monitor/replies.js +15 -7
  467. package/dist/slack/monitor/slash.js +22 -13
  468. package/dist/slack/resolve-channels.js +10 -5
  469. package/dist/slack/send.js +102 -12
  470. package/dist/slack/stream-mode.js +10 -0
  471. package/dist/slack/streaming.js +4 -2
  472. package/dist/telegram/accounts.js +19 -14
  473. package/dist/telegram/bot/helpers.js +3 -5
  474. package/dist/telegram/bot-access.js +35 -36
  475. package/dist/telegram/bot-handlers.js +120 -148
  476. package/dist/telegram/bot-message-context.js +68 -9
  477. package/dist/telegram/bot-message-dispatch.js +155 -90
  478. package/dist/telegram/bot-native-commands.js +16 -0
  479. package/dist/telegram/draft-stream.js +14 -1
  480. package/dist/telegram/inline-buttons.js +5 -15
  481. package/dist/telegram/monitor.js +11 -7
  482. package/dist/telegram/network-config.js +19 -7
  483. package/dist/telegram/send.js +3 -2
  484. package/dist/telegram/sent-message-cache.js +5 -6
  485. package/dist/telegram/status-reaction-variants.js +208 -0
  486. package/dist/telegram/sticker-cache.js +11 -9
  487. package/dist/terminal/theme.js +12 -12
  488. package/dist/tts/tts.js +80 -567
  489. package/dist/tui/components/chat-log.js +41 -8
  490. package/dist/tui/theme/theme.js +10 -12
  491. package/dist/tui/tui-local-shell.js +16 -6
  492. package/dist/tui/tui.js +58 -6
  493. package/dist/utils/account-id.js +2 -4
  494. package/dist/utils/boolean.js +10 -5
  495. package/dist/utils/directive-tags.js +11 -0
  496. package/dist/utils/queue-helpers.js +67 -12
  497. package/dist/web/auto-reply/deliver-reply.js +8 -4
  498. package/dist/web/auto-reply/mentions.js +10 -5
  499. package/dist/web/auto-reply/monitor/group-members.js +14 -7
  500. package/dist/web/auto-reply/monitor/process-message.js +45 -24
  501. package/dist/web/inbound/access-control.js +5 -2
  502. package/dist/web/login-qr.js +12 -6
  503. package/dist/web/media.js +123 -16
  504. package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
  505. package/extensions/bluebubbles/src/monitor.ts +208 -1950
  506. package/package.json +1 -1
@@ -1,17 +1,19 @@
1
1
  import { resolveIdentityNamePrefix } from "../../../agents/identity.js";
2
2
  import { resolveChunkMode, resolveTextChunkLimit } from "../../../auto-reply/chunk.js";
3
+ import { shouldComputeCommandAuthorized } from "../../../auto-reply/command-detection.js";
3
4
  import { formatInboundEnvelope, resolveEnvelopeFormatOptions, } from "../../../auto-reply/envelope.js";
4
5
  import { buildHistoryContextFromEntries, } from "../../../auto-reply/reply/history.js";
5
- import { dispatchReplyWithBufferedBlockDispatcher } from "../../../auto-reply/reply/provider-dispatcher.js";
6
- import { shouldComputeCommandAuthorized } from "../../../auto-reply/command-detection.js";
7
6
  import { finalizeInboundContext } from "../../../auto-reply/reply/inbound-context.js";
7
+ import { dispatchReplyWithBufferedBlockDispatcher } from "../../../auto-reply/reply/provider-dispatcher.js";
8
8
  import { toLocationContext } from "../../../channels/location.js";
9
- import { createReplyPrefixContext } from "../../../channels/reply-prefix.js";
10
- import { readSessionUpdatedAt, recordSessionMetaFromInbound, resolveStorePath, } from "../../../config/sessions.js";
9
+ import { createReplyPrefixOptions } from "../../../channels/reply-prefix.js";
11
10
  import { resolveMarkdownTableMode } from "../../../config/markdown-tables.js";
11
+ import { readSessionUpdatedAt, recordSessionMetaFromInbound, resolveStorePath, } from "../../../config/sessions.js";
12
12
  import { logVerbose, shouldLogVerbose } from "../../../globals.js";
13
+ import { getAgentScopedMediaLocalRoots } from "../../../media/local-roots.js";
13
14
  import { readChannelAllowFromStore } from "../../../pairing/pairing-store.js";
14
15
  import { jidToE164, normalizeE164 } from "../../../utils.js";
16
+ import { resolveWhatsAppAccount } from "../../accounts.js";
15
17
  import { newConnectionId } from "../../reconnect.js";
16
18
  import { formatError } from "../../session.js";
17
19
  import { deliverWebReply } from "../deliver-reply.js";
@@ -31,31 +33,39 @@ function normalizeAllowFromE164(values) {
31
33
  }
32
34
  async function resolveWhatsAppCommandAuthorized(params) {
33
35
  const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
34
- if (!useAccessGroups)
36
+ if (!useAccessGroups) {
35
37
  return true;
38
+ }
36
39
  const isGroup = params.msg.chatType === "group";
37
40
  const senderE164 = normalizeE164(isGroup ? (params.msg.senderE164 ?? "") : (params.msg.senderE164 ?? params.msg.from ?? ""));
38
- if (!senderE164)
41
+ if (!senderE164) {
39
42
  return false;
40
- const configuredAllowFrom = params.cfg.channels?.whatsapp?.allowFrom ?? [];
41
- const configuredGroupAllowFrom = params.cfg.channels?.whatsapp?.groupAllowFrom ??
42
- (configuredAllowFrom.length > 0 ? configuredAllowFrom : undefined);
43
+ }
44
+ const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.msg.accountId });
45
+ const dmPolicy = account.dmPolicy ?? "pairing";
46
+ const configuredAllowFrom = account.allowFrom ?? [];
47
+ const configuredGroupAllowFrom = account.groupAllowFrom ?? (configuredAllowFrom.length > 0 ? configuredAllowFrom : undefined);
43
48
  if (isGroup) {
44
- if (!configuredGroupAllowFrom || configuredGroupAllowFrom.length === 0)
49
+ if (!configuredGroupAllowFrom || configuredGroupAllowFrom.length === 0) {
45
50
  return false;
46
- if (configuredGroupAllowFrom.some((v) => String(v).trim() === "*"))
51
+ }
52
+ if (configuredGroupAllowFrom.some((v) => String(v).trim() === "*")) {
47
53
  return true;
54
+ }
48
55
  return normalizeAllowFromE164(configuredGroupAllowFrom).includes(senderE164);
49
56
  }
50
- const storeAllowFrom = await readChannelAllowFromStore("whatsapp").catch(() => []);
57
+ const storeAllowFrom = dmPolicy === "allowlist"
58
+ ? []
59
+ : await readChannelAllowFromStore("whatsapp", process.env, params.msg.accountId).catch(() => []);
51
60
  const combinedAllowFrom = Array.from(new Set([...(configuredAllowFrom ?? []), ...storeAllowFrom]));
52
61
  const allowFrom = combinedAllowFrom.length > 0
53
62
  ? combinedAllowFrom
54
63
  : params.msg.selfE164
55
64
  ? [params.msg.selfE164]
56
65
  : [];
57
- if (allowFrom.some((v) => String(v).trim() === "*"))
66
+ if (allowFrom.some((v) => String(v).trim() === "*")) {
58
67
  return true;
68
+ }
59
69
  return normalizeAllowFromE164(allowFrom).includes(senderE164);
60
70
  }
61
71
  export async function processMessage(params) {
@@ -83,21 +93,17 @@ export async function processMessage(params) {
83
93
  sender: m.sender,
84
94
  body: m.body,
85
95
  timestamp: m.timestamp,
86
- messageId: m.id,
87
96
  }));
88
97
  combinedBody = buildHistoryContextFromEntries({
89
98
  entries: historyEntries,
90
99
  currentMessage: combinedBody,
91
100
  excludeLast: false,
92
101
  formatEntry: (entry) => {
93
- const bodyWithId = entry.messageId
94
- ? `${entry.body}\n[message_id: ${entry.messageId}]`
95
- : entry.body;
96
102
  return formatInboundEnvelope({
97
103
  channel: "WhatsApp",
98
104
  from: conversationId,
99
105
  timestamp: entry.timestamp,
100
- body: bodyWithId,
106
+ body: entry.body,
101
107
  chatType: "group",
102
108
  senderLabel: entry.sender,
103
109
  envelope: envelopeOptions,
@@ -147,11 +153,13 @@ export async function processMessage(params) {
147
153
  }
148
154
  const dmRouteTarget = params.msg.chatType !== "group"
149
155
  ? (() => {
150
- if (params.msg.senderE164)
156
+ if (params.msg.senderE164) {
151
157
  return normalizeE164(params.msg.senderE164);
158
+ }
152
159
  // In direct chats, `msg.from` is already the canonical conversation id.
153
- if (params.msg.from.includes("@"))
160
+ if (params.msg.from.includes("@")) {
154
161
  return jidToE164(params.msg.from);
162
+ }
155
163
  return normalizeE164(params.msg.from);
156
164
  })()
157
165
  : undefined;
@@ -162,25 +170,37 @@ export async function processMessage(params) {
162
170
  channel: "whatsapp",
163
171
  accountId: params.route.accountId,
164
172
  });
173
+ const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.route.agentId);
165
174
  let didLogHeartbeatStrip = false;
166
175
  let didSendReply = false;
167
176
  const commandAuthorized = shouldComputeCommandAuthorized(params.msg.body, params.cfg)
168
177
  ? await resolveWhatsAppCommandAuthorized({ cfg: params.cfg, msg: params.msg })
169
178
  : undefined;
170
179
  const configuredResponsePrefix = params.cfg.messages?.responsePrefix;
171
- const prefixContext = createReplyPrefixContext({
180
+ const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
172
181
  cfg: params.cfg,
173
182
  agentId: params.route.agentId,
183
+ channel: "whatsapp",
184
+ accountId: params.route.accountId,
174
185
  });
175
186
  const isSelfChat = params.msg.chatType !== "group" &&
176
187
  Boolean(params.msg.selfE164) &&
177
188
  normalizeE164(params.msg.from) === normalizeE164(params.msg.selfE164 ?? "");
178
- const responsePrefix = prefixContext.responsePrefix ??
189
+ const responsePrefix = prefixOptions.responsePrefix ??
179
190
  (configuredResponsePrefix === undefined && isSelfChat
180
191
  ? (resolveIdentityNamePrefix(params.cfg, params.route.agentId) ?? "[poolbot]")
181
192
  : undefined);
193
+ const inboundHistory = params.msg.chatType === "group"
194
+ ? (params.groupHistory ?? params.groupHistories.get(params.groupHistoryKey) ?? []).map((entry) => ({
195
+ sender: entry.sender,
196
+ body: entry.body,
197
+ timestamp: entry.timestamp,
198
+ }))
199
+ : undefined;
182
200
  const ctxPayload = finalizeInboundContext({
183
201
  Body: combinedBody,
202
+ BodyForAgent: params.msg.body,
203
+ InboundHistory: inboundHistory,
184
204
  RawBody: params.msg.body,
185
205
  CommandBody: params.msg.body,
186
206
  From: params.msg.from,
@@ -243,8 +263,8 @@ export async function processMessage(params) {
243
263
  cfg: params.cfg,
244
264
  replyResolver: params.replyResolver,
245
265
  dispatcherOptions: {
266
+ ...prefixOptions,
246
267
  responsePrefix,
247
- responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
248
268
  onHeartbeatStrip: () => {
249
269
  if (!didLogHeartbeatStrip) {
250
270
  didLogHeartbeatStrip = true;
@@ -255,6 +275,7 @@ export async function processMessage(params) {
255
275
  await deliverWebReply({
256
276
  replyResult: payload,
257
277
  msg: params.msg,
278
+ mediaLocalRoots,
258
279
  maxMediaBytes: params.maxMediaBytes,
259
280
  textLimit,
260
281
  chunkMode,
@@ -299,7 +320,7 @@ export async function processMessage(params) {
299
320
  disableBlockStreaming: typeof params.cfg.channels?.whatsapp?.blockStreaming === "boolean"
300
321
  ? !params.cfg.channels.whatsapp.blockStreaming
301
322
  : undefined,
302
- onModelSelected: prefixContext.onModelSelected,
323
+ onModelSelected,
303
324
  },
304
325
  });
305
326
  if (!queuedFinal) {
@@ -11,9 +11,11 @@ export async function checkInboundAccessControl(params) {
11
11
  cfg,
12
12
  accountId: params.accountId,
13
13
  });
14
- const dmPolicy = cfg.channels?.whatsapp?.dmPolicy ?? "pairing";
14
+ const dmPolicy = account.dmPolicy ?? "pairing";
15
15
  const configuredAllowFrom = account.allowFrom;
16
- const storeAllowFrom = await readChannelAllowFromStore("whatsapp").catch(() => []);
16
+ const storeAllowFrom = dmPolicy === "allowlist"
17
+ ? []
18
+ : await readChannelAllowFromStore("whatsapp", process.env, account.accountId).catch(() => []);
17
19
  // Without user config, default to self-only DM access so the owner can talk to themselves.
18
20
  const combinedAllowFrom = Array.from(new Set([...(configuredAllowFrom ?? []), ...storeAllowFrom]));
19
21
  const defaultAllowFrom = combinedAllowFrom.length === 0 && params.selfE164 ? [params.selfE164] : undefined;
@@ -107,6 +109,7 @@ export async function checkInboundAccessControl(params) {
107
109
  const { code, created } = await upsertChannelPairingRequest({
108
110
  channel: "whatsapp",
109
111
  id: candidate,
112
+ accountId: account.accountId,
110
113
  meta: { name: (params.pushName ?? "").trim() || undefined },
111
114
  });
112
115
  if (created) {
@@ -34,20 +34,23 @@ function attachLoginWaiter(accountId, login) {
34
34
  login.waitPromise = waitForWaConnection(login.sock)
35
35
  .then(() => {
36
36
  const current = activeLogins.get(accountId);
37
- if (current?.id === login.id)
37
+ if (current?.id === login.id) {
38
38
  current.connected = true;
39
+ }
39
40
  })
40
41
  .catch((err) => {
41
42
  const current = activeLogins.get(accountId);
42
- if (current?.id !== login.id)
43
+ if (current?.id !== login.id) {
43
44
  return;
45
+ }
44
46
  current.error = formatError(err);
45
47
  current.errorStatus = getStatusCode(err);
46
48
  });
47
49
  }
48
50
  async function restartLoginSocket(login, runtime) {
49
- if (login.restartAttempted)
51
+ if (login.restartAttempted) {
50
52
  return false;
53
+ }
51
54
  login.restartAttempted = true;
52
55
  runtime.log(info("WhatsApp asked for a restart after pairing (code 515); retrying connection once…"));
53
56
  closeSocket(login.sock);
@@ -103,12 +106,14 @@ export async function startWebLoginWithQr(opts = {}) {
103
106
  sock = await createWaSocket(false, Boolean(opts.verbose), {
104
107
  authDir: account.authDir,
105
108
  onQr: (qr) => {
106
- if (pendingQr)
109
+ if (pendingQr) {
107
110
  return;
111
+ }
108
112
  pendingQr = qr;
109
113
  const current = activeLogins.get(account.accountId);
110
- if (current && !current.qr)
114
+ if (current && !current.qr) {
111
115
  current.qr = qr;
116
+ }
112
117
  clearTimeout(qrTimer);
113
118
  runtime.log(info("WhatsApp QR received."));
114
119
  resolveQr?.(qr);
@@ -135,8 +140,9 @@ export async function startWebLoginWithQr(opts = {}) {
135
140
  verbose: Boolean(opts.verbose),
136
141
  };
137
142
  activeLogins.set(account.accountId, login);
138
- if (pendingQr && !login.qr)
143
+ if (pendingQr && !login.qr) {
139
144
  login.qr = pendingQr;
145
+ }
140
146
  attachLoginWaiter(account.accountId, login);
141
147
  let qr;
142
148
  try {
package/dist/web/media.js CHANGED
@@ -2,15 +2,71 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { logVerbose, shouldLogVerbose } from "../globals.js";
5
+ import { SafeOpenError, readLocalFileSafely } from "../infra/fs-safe.js";
5
6
  import { maxBytesForKind, mediaKindFromMime } from "../media/constants.js";
6
- import { resolveUserPath } from "../utils.js";
7
7
  import { fetchRemoteMedia } from "../media/fetch.js";
8
- import { getDefaultMediaLocalRoots } from "../media/local-roots.js";
9
8
  import { convertHeicToJpeg, hasAlphaChannel, optimizeImageToPng, resizeToJpeg, } from "../media/image-ops.js";
9
+ import { getDefaultMediaLocalRoots } from "../media/local-roots.js";
10
10
  import { detectMime, extensionForMime } from "../media/mime.js";
11
+ import { resolveUserPath } from "../utils.js";
12
+ export class LocalMediaAccessError extends Error {
13
+ code;
14
+ constructor(code, message, options) {
15
+ super(message, options);
16
+ this.code = code;
17
+ this.name = "LocalMediaAccessError";
18
+ }
19
+ }
11
20
  export function getDefaultLocalRoots() {
12
21
  return getDefaultMediaLocalRoots();
13
22
  }
23
+ async function assertLocalMediaAllowed(mediaPath, localRoots) {
24
+ if (localRoots === "any") {
25
+ return;
26
+ }
27
+ const roots = localRoots ?? getDefaultLocalRoots();
28
+ // Resolve symlinks so a symlink under /tmp pointing to /etc/passwd is caught.
29
+ let resolved;
30
+ try {
31
+ resolved = await fs.realpath(mediaPath);
32
+ }
33
+ catch {
34
+ resolved = path.resolve(mediaPath);
35
+ }
36
+ // Hardening: the default allowlist includes the Pool Bot temp dir, and tests/CI may
37
+ // override the state dir into tmp. Avoid accidentally allowing per-agent
38
+ // `workspace-*` state roots via the temp-root prefix match; require explicit
39
+ // localRoots for those.
40
+ if (localRoots === undefined) {
41
+ const workspaceRoot = roots.find((root) => path.basename(root) === "workspace");
42
+ if (workspaceRoot) {
43
+ const stateDir = path.dirname(workspaceRoot);
44
+ const rel = path.relative(stateDir, resolved);
45
+ if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
46
+ const firstSegment = rel.split(path.sep)[0] ?? "";
47
+ if (firstSegment.startsWith("workspace-")) {
48
+ throw new LocalMediaAccessError("path-not-allowed", `Local media path is not under an allowed directory: ${mediaPath}`);
49
+ }
50
+ }
51
+ }
52
+ }
53
+ for (const root of roots) {
54
+ let resolvedRoot;
55
+ try {
56
+ resolvedRoot = await fs.realpath(root);
57
+ }
58
+ catch {
59
+ resolvedRoot = path.resolve(root);
60
+ }
61
+ if (resolvedRoot === path.parse(resolvedRoot).root) {
62
+ throw new LocalMediaAccessError("invalid-root", `Invalid localRoots entry (refuses filesystem root): ${root}. Pass a narrower directory.`);
63
+ }
64
+ if (resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path.sep)) {
65
+ return;
66
+ }
67
+ }
68
+ throw new LocalMediaAccessError("path-not-allowed", `Local media path is not under an allowed directory: ${mediaPath}`);
69
+ }
14
70
  const HEIC_MIME_RE = /^image\/hei[cf]$/i;
15
71
  const HEIC_EXT_RE = /\.(heic|heif)$/i;
16
72
  const MB = 1024 * 1024;
@@ -24,18 +80,22 @@ function formatCapReduce(label, cap, size) {
24
80
  return `${label} could not be reduced below ${formatMb(cap, 0)}MB (got ${formatMb(size)}MB)`;
25
81
  }
26
82
  function isHeicSource(opts) {
27
- if (opts.contentType && HEIC_MIME_RE.test(opts.contentType.trim()))
83
+ if (opts.contentType && HEIC_MIME_RE.test(opts.contentType.trim())) {
28
84
  return true;
29
- if (opts.fileName && HEIC_EXT_RE.test(opts.fileName.trim()))
85
+ }
86
+ if (opts.fileName && HEIC_EXT_RE.test(opts.fileName.trim())) {
30
87
  return true;
88
+ }
31
89
  return false;
32
90
  }
33
91
  function toJpegFileName(fileName) {
34
- if (!fileName)
92
+ if (!fileName) {
35
93
  return undefined;
94
+ }
36
95
  const trimmed = fileName.trim();
37
- if (!trimmed)
96
+ if (!trimmed) {
38
97
  return fileName;
98
+ }
39
99
  const parsed = path.parse(trimmed);
40
100
  if (!parsed.ext || HEIC_EXT_RE.test(parsed.ext)) {
41
101
  return path.format({ dir: parsed.dir, name: parsed.name || trimmed, ext: ".jpg" });
@@ -43,10 +103,12 @@ function toJpegFileName(fileName) {
43
103
  return path.format({ dir: parsed.dir, name: parsed.name, ext: ".jpg" });
44
104
  }
45
105
  function logOptimizedImage(params) {
46
- if (!shouldLogVerbose())
106
+ if (!shouldLogVerbose()) {
47
107
  return;
48
- if (params.optimized.optimizedSize >= params.originalSize)
108
+ }
109
+ if (params.optimized.optimizedSize >= params.originalSize) {
49
110
  return;
111
+ }
50
112
  if (params.optimized.format === "png") {
51
113
  logVerbose(`Optimized PNG (preserving alpha) from ${formatMb(params.originalSize)}MB to ${formatMb(params.optimized.optimizedSize)}MB (side≤${params.optimized.resizeSide}px)`);
52
114
  return;
@@ -70,14 +132,17 @@ async function optimizeImageWithFallback(params) {
70
132
  return { ...optimized, format: "jpeg" };
71
133
  }
72
134
  async function loadWebMediaInternal(mediaUrl, options = {}) {
73
- const { maxBytes, optimizeImages = true } = options;
135
+ const { maxBytes, optimizeImages = true, ssrfPolicy, localRoots, sandboxValidated = false, readFile: readFileOverride, } = options;
136
+ // Strip MEDIA: prefix used by agent tools (e.g. TTS) to tag media paths.
137
+ // Be lenient: LLM output may add extra whitespace (e.g. " MEDIA : /tmp/x.png").
138
+ mediaUrl = mediaUrl.replace(/^\s*MEDIA\s*:\s*/i, "");
74
139
  // Use fileURLToPath for proper handling of file:// URLs (handles file://localhost/path, etc.)
75
140
  if (mediaUrl.startsWith("file://")) {
76
141
  try {
77
142
  mediaUrl = fileURLToPath(mediaUrl);
78
143
  }
79
144
  catch {
80
- throw new Error(`Invalid file:// URL: ${mediaUrl}`);
145
+ throw new LocalMediaAccessError("invalid-file-url", `Invalid file:// URL: ${mediaUrl}`);
81
146
  }
82
147
  }
83
148
  const optimizeAndClampImage = async (buffer, cap, meta) => {
@@ -133,7 +198,15 @@ async function loadWebMediaInternal(mediaUrl, options = {}) {
133
198
  };
134
199
  };
135
200
  if (/^https?:\/\//i.test(mediaUrl)) {
136
- const fetched = await fetchRemoteMedia({ url: mediaUrl });
201
+ // Enforce a download cap during fetch to avoid unbounded memory usage.
202
+ // For optimized images, allow fetching larger payloads before compression.
203
+ const defaultFetchCap = maxBytesForKind("unknown");
204
+ const fetchCap = maxBytes === undefined
205
+ ? defaultFetchCap
206
+ : optimizeImages
207
+ ? Math.max(maxBytes, defaultFetchCap)
208
+ : maxBytes;
209
+ const fetched = await fetchRemoteMedia({ url: mediaUrl, maxBytes: fetchCap, ssrfPolicy });
137
210
  const { buffer, contentType, fileName } = fetched;
138
211
  const kind = mediaKindFromMime(contentType);
139
212
  return await clampAndFinalize({ buffer, contentType, kind, fileName });
@@ -142,15 +215,45 @@ async function loadWebMediaInternal(mediaUrl, options = {}) {
142
215
  if (mediaUrl.startsWith("~")) {
143
216
  mediaUrl = resolveUserPath(mediaUrl);
144
217
  }
218
+ if ((sandboxValidated || localRoots === "any") && !readFileOverride) {
219
+ throw new LocalMediaAccessError("unsafe-bypass", "Refusing localRoots bypass without readFile override. Use sandboxValidated with readFile, or pass explicit localRoots.");
220
+ }
221
+ // Guard local reads against allowed directory roots to prevent file exfiltration.
222
+ if (!(sandboxValidated || localRoots === "any")) {
223
+ await assertLocalMediaAllowed(mediaUrl, localRoots);
224
+ }
145
225
  // Local path
146
- const data = await fs.readFile(mediaUrl);
226
+ let data;
227
+ if (readFileOverride) {
228
+ data = await readFileOverride(mediaUrl);
229
+ }
230
+ else {
231
+ try {
232
+ data = (await readLocalFileSafely({ filePath: mediaUrl })).buffer;
233
+ }
234
+ catch (err) {
235
+ if (err instanceof SafeOpenError) {
236
+ if (err.code === "not-found") {
237
+ throw new LocalMediaAccessError("not-found", `Local media file not found: ${mediaUrl}`, {
238
+ cause: err,
239
+ });
240
+ }
241
+ if (err.code === "not-file") {
242
+ throw new LocalMediaAccessError("not-file", `Local media path is not a file: ${mediaUrl}`, { cause: err });
243
+ }
244
+ throw new LocalMediaAccessError("invalid-path", `Local media path is not safe to read: ${mediaUrl}`, { cause: err });
245
+ }
246
+ throw err;
247
+ }
248
+ }
147
249
  const mime = await detectMime({ buffer: data, filePath: mediaUrl });
148
250
  const kind = mediaKindFromMime(mime);
149
251
  let fileName = path.basename(mediaUrl) || undefined;
150
252
  if (fileName && !path.extname(fileName) && mime) {
151
253
  const ext = extensionForMime(mime);
152
- if (ext)
254
+ if (ext) {
153
255
  fileName = `${fileName}${ext}`;
256
+ }
154
257
  }
155
258
  return await clampAndFinalize({
156
259
  buffer: data,
@@ -159,11 +262,13 @@ async function loadWebMediaInternal(mediaUrl, options = {}) {
159
262
  fileName,
160
263
  });
161
264
  }
162
- export async function loadWebMedia(mediaUrl, maxBytesOrOptions) {
265
+ export async function loadWebMedia(mediaUrl, maxBytesOrOptions, options) {
163
266
  if (typeof maxBytesOrOptions === "number" || maxBytesOrOptions === undefined) {
164
267
  return await loadWebMediaInternal(mediaUrl, {
165
268
  maxBytes: maxBytesOrOptions,
166
269
  optimizeImages: true,
270
+ ssrfPolicy: options?.ssrfPolicy,
271
+ localRoots: options?.localRoots,
167
272
  });
168
273
  }
169
274
  return await loadWebMediaInternal(mediaUrl, {
@@ -171,11 +276,13 @@ export async function loadWebMedia(mediaUrl, maxBytesOrOptions) {
171
276
  optimizeImages: maxBytesOrOptions.optimizeImages ?? true,
172
277
  });
173
278
  }
174
- export async function loadWebMediaRaw(mediaUrl, maxBytesOrOptions) {
279
+ export async function loadWebMediaRaw(mediaUrl, maxBytesOrOptions, options) {
175
280
  if (typeof maxBytesOrOptions === "number" || maxBytesOrOptions === undefined) {
176
281
  return await loadWebMediaInternal(mediaUrl, {
177
282
  maxBytes: maxBytesOrOptions,
178
283
  optimizeImages: false,
284
+ ssrfPolicy: options?.ssrfPolicy,
285
+ localRoots: options?.localRoots,
179
286
  });
180
287
  }
181
288
  return await loadWebMediaInternal(mediaUrl, {
@@ -191,7 +298,7 @@ export async function optimizeImageToJpeg(buffer, maxBytes, opts = {}) {
191
298
  source = await convertHeicToJpeg(buffer);
192
299
  }
193
300
  catch (err) {
194
- throw new Error(`HEIC image conversion failed: ${String(err)}`);
301
+ throw new Error(`HEIC image conversion failed: ${String(err)}`, { cause: err });
195
302
  }
196
303
  }
197
304
  const sides = [2048, 1536, 1280, 1024, 800];