@poolzin/pool-bot 2026.2.21 → 2026.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -1,11 +1,8 @@
1
- import fs from "node:fs/promises";
2
1
  import path from "node:path";
3
2
  import { fileURLToPath } from "node:url";
4
- import { assertSandboxPath } from "../../sandbox-paths.js";
5
- import { sanitizeImageBlocks } from "../../tool-images.js";
6
- import { extractTextFromMessage } from "../../../tui/tui-formatters.js";
7
- import { loadWebMedia } from "../../../web/media.js";
8
3
  import { resolveUserPath } from "../../../utils.js";
4
+ import { loadWebMedia } from "../../../web/media.js";
5
+ import { sanitizeImageBlocks } from "../../tool-images.js";
9
6
  import { log } from "../logger.js";
10
7
  /**
11
8
  * Common image file extensions for detection.
@@ -29,8 +26,8 @@ function isImageExtension(filePath) {
29
26
  const ext = path.extname(filePath).toLowerCase();
30
27
  return IMAGE_EXTENSIONS.has(ext);
31
28
  }
32
- async function sanitizeImagesWithLog(images, label) {
33
- const { images: sanitized, dropped } = await sanitizeImageBlocks(images, label);
29
+ async function sanitizeImagesWithLog(images, label, imageSanitization) {
30
+ const { images: sanitized, dropped } = await sanitizeImageBlocks(images, label, imageSanitization);
34
31
  if (dropped > 0) {
35
32
  log.warn(`Native image: dropped ${dropped} image(s) after sanitization (${label}).`);
36
33
  }
@@ -55,12 +52,15 @@ export function detectImageReferences(prompt) {
55
52
  // Helper to add a path ref
56
53
  const addPathRef = (raw) => {
57
54
  const trimmed = raw.trim();
58
- if (!trimmed || seen.has(trimmed.toLowerCase()))
55
+ if (!trimmed || seen.has(trimmed.toLowerCase())) {
59
56
  return;
60
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://"))
57
+ }
58
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
61
59
  return;
62
- if (!isImageExtension(trimmed))
60
+ }
61
+ if (!isImageExtension(trimmed)) {
63
62
  return;
63
+ }
64
64
  seen.add(trimmed.toLowerCase());
65
65
  const resolved = trimmed.startsWith("~") ? resolveUserPath(trimmed) : trimmed;
66
66
  refs.push({ raw: trimmed, type: "path", resolved });
@@ -89,16 +89,18 @@ export function detectImageReferences(prompt) {
89
89
  const messageImagePattern = /\[Image:\s*source:\s*([^\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))\]/gi;
90
90
  while ((match = messageImagePattern.exec(prompt)) !== null) {
91
91
  const raw = match[1]?.trim();
92
- if (raw)
92
+ if (raw) {
93
93
  addPathRef(raw);
94
+ }
94
95
  }
95
96
  // Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only.
96
97
  // Pattern for file:// URLs - treat as paths since loadWebMedia handles them
97
98
  const fileUrlPattern = /file:\/\/[^\s<>"'`\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif)/gi;
98
99
  while ((match = fileUrlPattern.exec(prompt)) !== null) {
99
100
  const raw = match[0];
100
- if (seen.has(raw.toLowerCase()))
101
+ if (seen.has(raw.toLowerCase())) {
101
102
  continue;
103
+ }
102
104
  seen.add(raw.toLowerCase());
103
105
  // Use fileURLToPath for proper handling (e.g., file://localhost/path)
104
106
  try {
@@ -118,8 +120,9 @@ export function detectImageReferences(prompt) {
118
120
  const pathPattern = /(?:^|\s|["'`(])((\.\.?\/|[~/])[^\s"'`()[\]]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))/gi;
119
121
  while ((match = pathPattern.exec(prompt)) !== null) {
120
122
  // Use capture group 1 (the path without delimiter prefix); skip if undefined
121
- if (match[1])
123
+ if (match[1]) {
122
124
  addPathRef(match[1]);
125
+ }
123
126
  }
124
127
  return refs;
125
128
  }
@@ -139,43 +142,33 @@ export async function loadImageFromRef(ref, workspaceDir, options) {
139
142
  log.debug(`Native image: rejecting remote URL (local-only): ${ref.resolved}`);
140
143
  return null;
141
144
  }
142
- // For file paths, resolve relative to the appropriate root:
143
- // - When sandbox is enabled, resolve relative to sandboxRoot for security
144
- // - Otherwise, resolve relative to workspaceDir
145
- // Note: ref.resolved may already be absolute (e.g., after ~ expansion in detectImageReferences),
146
- // in which case we skip relative resolution.
147
- if (ref.type === "path" && !path.isAbsolute(targetPath)) {
148
- const resolveRoot = options?.sandboxRoot ?? workspaceDir;
149
- targetPath = path.resolve(resolveRoot, targetPath);
150
- }
151
- // Enforce sandbox restrictions if sandboxRoot is set
152
- if (ref.type === "path" && options?.sandboxRoot) {
153
- try {
154
- const validated = await assertSandboxPath({
155
- filePath: targetPath,
156
- cwd: options.sandboxRoot,
157
- root: options.sandboxRoot,
158
- });
159
- targetPath = validated.resolved;
160
- }
161
- catch (err) {
162
- // Log the actual error for debugging (sandbox violation or other path error)
163
- log.debug(`Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`);
164
- return null;
165
- }
166
- }
167
- // Check file exists for local paths
145
+ // Resolve paths relative to sandbox or workspace as needed
168
146
  if (ref.type === "path") {
169
- try {
170
- await fs.stat(targetPath);
147
+ if (options?.sandbox) {
148
+ try {
149
+ const resolved = options.sandbox.bridge.resolvePath({
150
+ filePath: targetPath,
151
+ cwd: options.sandbox.root,
152
+ });
153
+ targetPath = resolved.hostPath;
154
+ }
155
+ catch (err) {
156
+ log.debug(`Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`);
157
+ return null;
158
+ }
171
159
  }
172
- catch {
173
- log.debug(`Native image: file not found: ${targetPath}`);
174
- return null;
160
+ else if (!path.isAbsolute(targetPath)) {
161
+ targetPath = path.resolve(workspaceDir, targetPath);
175
162
  }
176
163
  }
177
164
  // loadWebMedia handles local file paths (including file:// URLs)
178
- const media = await loadWebMedia(targetPath, options?.maxBytes);
165
+ const media = options?.sandbox
166
+ ? await loadWebMedia(targetPath, {
167
+ maxBytes: options.maxBytes,
168
+ sandboxValidated: true,
169
+ readFile: (filePath) => options.sandbox.bridge.readFile({ filePath, cwd: options.sandbox.root }),
170
+ })
171
+ : await loadWebMedia(targetPath, options?.maxBytes);
179
172
  if (media.kind !== "image") {
180
173
  log.debug(`Native image: not an image file: ${targetPath} (got ${media.kind})`);
181
174
  return null;
@@ -201,6 +194,29 @@ export async function loadImageFromRef(ref, workspaceDir, options) {
201
194
  export function modelSupportsImages(model) {
202
195
  return model.input?.includes("image") ?? false;
203
196
  }
197
+ function extractTextFromMessage(message) {
198
+ if (!message || typeof message !== "object") {
199
+ return "";
200
+ }
201
+ const content = message.content;
202
+ if (typeof content === "string") {
203
+ return content;
204
+ }
205
+ if (!Array.isArray(content)) {
206
+ return "";
207
+ }
208
+ const textParts = [];
209
+ for (const part of content) {
210
+ if (!part || typeof part !== "object") {
211
+ continue;
212
+ }
213
+ const record = part;
214
+ if (record.type === "text" && typeof record.text === "string") {
215
+ textParts.push(record.text);
216
+ }
217
+ }
218
+ return textParts.join("\n").trim();
219
+ }
204
220
  /**
205
221
  * Extracts image references from conversation history messages.
206
222
  * Scans user messages for image paths/URLs that can be loaded.
@@ -216,32 +232,39 @@ function detectImagesFromHistory(messages) {
216
232
  const allRefs = [];
217
233
  const seen = new Set();
218
234
  const messageHasImageContent = (msg) => {
219
- if (!msg || typeof msg !== "object")
235
+ if (!msg || typeof msg !== "object") {
220
236
  return false;
237
+ }
221
238
  const content = msg.content;
222
- if (!Array.isArray(content))
239
+ if (!Array.isArray(content)) {
223
240
  return false;
241
+ }
224
242
  return content.some((part) => part != null && typeof part === "object" && part.type === "image");
225
243
  };
226
244
  for (let i = 0; i < messages.length; i++) {
227
245
  const msg = messages[i];
228
- if (!msg || typeof msg !== "object")
246
+ if (!msg || typeof msg !== "object") {
229
247
  continue;
248
+ }
230
249
  const message = msg;
231
250
  // Only scan user messages for image references
232
- if (message.role !== "user")
251
+ if (message.role !== "user") {
233
252
  continue;
253
+ }
234
254
  // Skip if message already has image content (prevents reloading each turn)
235
- if (messageHasImageContent(msg))
255
+ if (messageHasImageContent(msg)) {
236
256
  continue;
257
+ }
237
258
  const text = extractTextFromMessage(msg);
238
- if (!text)
259
+ if (!text) {
239
260
  continue;
261
+ }
240
262
  const refs = detectImageReferences(text);
241
263
  for (const ref of refs) {
242
264
  const key = ref.resolved.toLowerCase();
243
- if (seen.has(key))
265
+ if (seen.has(key)) {
244
266
  continue;
267
+ }
245
268
  seen.add(key);
246
269
  allRefs.push({ ...ref, messageIndex: i });
247
270
  }
@@ -302,7 +325,7 @@ export async function detectAndLoadPromptImages(params) {
302
325
  for (const ref of allRefs) {
303
326
  const image = await loadImageFromRef(ref, params.workspaceDir, {
304
327
  maxBytes: params.maxBytes,
305
- sandboxRoot: params.sandboxRoot,
328
+ sandbox: params.sandbox,
306
329
  });
307
330
  if (image) {
308
331
  if (ref.messageIndex !== undefined) {
@@ -326,10 +349,13 @@ export async function detectAndLoadPromptImages(params) {
326
349
  skippedCount++;
327
350
  }
328
351
  }
329
- const sanitizedPromptImages = await sanitizeImagesWithLog(promptImages, "prompt:images");
352
+ const imageSanitization = {
353
+ maxDimensionPx: params.maxDimensionPx,
354
+ };
355
+ const sanitizedPromptImages = await sanitizeImagesWithLog(promptImages, "prompt:images", imageSanitization);
330
356
  const sanitizedHistoryImagesByIndex = new Map();
331
357
  for (const [index, images] of historyImagesByIndex) {
332
- const sanitized = await sanitizeImagesWithLog(images, `history:images:${index}`);
358
+ const sanitized = await sanitizeImagesWithLog(images, `history:images:${index}`, imageSanitization);
333
359
  if (sanitized.length > 0) {
334
360
  sanitizedHistoryImagesByIndex.set(index, sanitized);
335
361
  }
@@ -1,8 +1,35 @@
1
1
  import { parseReplyDirectives } from "../../../auto-reply/reply/reply-directives.js";
2
2
  import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../../../auto-reply/tokens.js";
3
3
  import { formatToolAggregate } from "../../../auto-reply/tool-meta.js";
4
- import { formatAssistantErrorText, formatRawAssistantErrorForUi, getApiErrorPayloadFingerprint, isRawApiErrorPayload, normalizeTextForComparison, } from "../../pi-embedded-helpers.js";
4
+ import { BILLING_ERROR_USER_MESSAGE, formatAssistantErrorText, formatRawAssistantErrorForUi, getApiErrorPayloadFingerprint, isRawApiErrorPayload, normalizeTextForComparison, } from "../../pi-embedded-helpers.js";
5
5
  import { extractAssistantText, extractAssistantThinking, formatReasoningMessage, } from "../../pi-embedded-utils.js";
6
+ import { isLikelyMutatingToolName } from "../../tool-mutation.js";
7
+ const RECOVERABLE_TOOL_ERROR_KEYWORDS = [
8
+ "required",
9
+ "missing",
10
+ "invalid",
11
+ "must be",
12
+ "must have",
13
+ "needs",
14
+ "requires",
15
+ ];
16
+ function isRecoverableToolError(error) {
17
+ const errorLower = (error ?? "").toLowerCase();
18
+ return RECOVERABLE_TOOL_ERROR_KEYWORDS.some((keyword) => errorLower.includes(keyword));
19
+ }
20
+ function shouldShowToolErrorWarning(params) {
21
+ if (params.suppressToolErrorWarnings) {
22
+ return false;
23
+ }
24
+ const isMutatingToolError = params.lastToolError.mutatingAction ?? isLikelyMutatingToolName(params.lastToolError.toolName);
25
+ if (isMutatingToolError) {
26
+ return true;
27
+ }
28
+ if (params.suppressToolErrors) {
29
+ return false;
30
+ }
31
+ return !params.hasUserFacingReply && !isRecoverableToolError(params.lastToolError.error);
32
+ }
6
33
  export function buildEmbeddedRunPayloads(params) {
7
34
  const replyItems = [];
8
35
  const useMarkdown = params.toolResultFormat === "markdown";
@@ -11,6 +38,7 @@ export function buildEmbeddedRunPayloads(params) {
11
38
  ? formatAssistantErrorText(params.lastAssistant, {
12
39
  cfg: params.config,
13
40
  sessionKey: params.sessionKey,
41
+ provider: params.provider,
14
42
  })
15
43
  : undefined;
16
44
  const rawErrorMessage = lastAssistantErrored
@@ -29,9 +57,11 @@ export function buildEmbeddedRunPayloads(params) {
29
57
  ? normalizeTextForComparison(rawErrorMessage)
30
58
  : null;
31
59
  const normalizedErrorText = errorText ? normalizeTextForComparison(errorText) : null;
60
+ const normalizedGenericBillingErrorText = normalizeTextForComparison(BILLING_ERROR_USER_MESSAGE);
32
61
  const genericErrorText = "The AI service returned an error. Please try again.";
33
- if (errorText)
62
+ if (errorText) {
34
63
  replyItems.push({ text: errorText, isError: true });
64
+ }
35
65
  const inlineToolResults = params.inlineToolResultsAllowed && params.verboseLevel !== "off" && params.toolMetas.length > 0;
36
66
  if (inlineToolResults) {
37
67
  for (const { toolName, meta } of params.toolMetas) {
@@ -54,40 +84,55 @@ export function buildEmbeddedRunPayloads(params) {
54
84
  const reasoningText = params.lastAssistant && params.reasoningLevel === "on"
55
85
  ? formatReasoningMessage(extractAssistantThinking(params.lastAssistant))
56
86
  : "";
57
- if (reasoningText)
87
+ if (reasoningText) {
58
88
  replyItems.push({ text: reasoningText });
89
+ }
59
90
  const fallbackAnswerText = params.lastAssistant ? extractAssistantText(params.lastAssistant) : "";
60
91
  const shouldSuppressRawErrorText = (text) => {
61
- if (!lastAssistantErrored)
92
+ if (!lastAssistantErrored) {
62
93
  return false;
94
+ }
63
95
  const trimmed = text.trim();
64
- if (!trimmed)
96
+ if (!trimmed) {
65
97
  return false;
98
+ }
66
99
  if (errorText) {
67
100
  const normalized = normalizeTextForComparison(trimmed);
68
- if (normalized && normalizedErrorText && normalized === normalizedErrorText)
101
+ if (normalized && normalizedErrorText && normalized === normalizedErrorText) {
69
102
  return true;
70
- if (trimmed === genericErrorText)
103
+ }
104
+ if (trimmed === genericErrorText) {
105
+ return true;
106
+ }
107
+ if (normalized &&
108
+ normalizedGenericBillingErrorText &&
109
+ normalized === normalizedGenericBillingErrorText) {
71
110
  return true;
111
+ }
72
112
  }
73
- if (rawErrorMessage && trimmed === rawErrorMessage)
113
+ if (rawErrorMessage && trimmed === rawErrorMessage) {
74
114
  return true;
75
- if (formattedRawErrorMessage && trimmed === formattedRawErrorMessage)
115
+ }
116
+ if (formattedRawErrorMessage && trimmed === formattedRawErrorMessage) {
76
117
  return true;
118
+ }
77
119
  if (normalizedRawErrorText) {
78
120
  const normalized = normalizeTextForComparison(trimmed);
79
- if (normalized && normalized === normalizedRawErrorText)
121
+ if (normalized && normalized === normalizedRawErrorText) {
80
122
  return true;
123
+ }
81
124
  }
82
125
  if (normalizedFormattedRawErrorMessage) {
83
126
  const normalized = normalizeTextForComparison(trimmed);
84
- if (normalized && normalized === normalizedFormattedRawErrorMessage)
127
+ if (normalized && normalized === normalizedFormattedRawErrorMessage) {
85
128
  return true;
129
+ }
86
130
  }
87
131
  if (rawErrorFingerprint) {
88
132
  const fingerprint = getApiErrorPayloadFingerprint(trimmed);
89
- if (fingerprint && fingerprint === rawErrorFingerprint)
133
+ if (fingerprint && fingerprint === rawErrorFingerprint) {
90
134
  return true;
135
+ }
91
136
  }
92
137
  return isRawApiErrorPayload(trimmed);
93
138
  };
@@ -96,6 +141,7 @@ export function buildEmbeddedRunPayloads(params) {
96
141
  : fallbackAnswerText
97
142
  ? [fallbackAnswerText]
98
143
  : []).filter((text) => !shouldSuppressRawErrorText(text));
144
+ let hasUserFacingAssistantReply = false;
99
145
  for (const text of answerTexts) {
100
146
  const { text: cleanedText, mediaUrls, audioAsVoice, replyToId, replyToTag, replyToCurrent, } = parseReplyDirectives(text);
101
147
  if (!cleanedText && (!mediaUrls || mediaUrls.length === 0) && !audioAsVoice) {
@@ -109,35 +155,37 @@ export function buildEmbeddedRunPayloads(params) {
109
155
  replyToTag,
110
156
  replyToCurrent,
111
157
  });
158
+ hasUserFacingAssistantReply = true;
112
159
  }
113
160
  if (params.lastToolError) {
114
- const lastAssistantHasToolCalls = Array.isArray(params.lastAssistant?.content) &&
115
- params.lastAssistant?.content.some((block) => block && typeof block === "object"
116
- ? block.type === "toolCall"
117
- : false);
118
- const lastAssistantWasToolUse = params.lastAssistant?.stopReason === "toolUse";
119
- const hasUserFacingReply = replyItems.length > 0 && !lastAssistantHasToolCalls && !lastAssistantWasToolUse;
120
- // Check if this is a recoverable/internal tool error that shouldn't be shown to users
121
- // when there's already a user-facing reply (the model should have retried).
122
- const errorLower = (params.lastToolError.error ?? "").toLowerCase();
123
- const isRecoverableError = errorLower.includes("required") ||
124
- errorLower.includes("missing") ||
125
- errorLower.includes("invalid") ||
126
- errorLower.includes("must be") ||
127
- errorLower.includes("must have") ||
128
- errorLower.includes("needs") ||
129
- errorLower.includes("requires");
130
- // Show tool errors only when:
131
- // 1. There's no user-facing reply AND the error is not recoverable
132
- // Recoverable errors (validation, missing params) are already in the model's context
133
- // and shouldn't be surfaced to users since the model should retry.
134
- if (!hasUserFacingReply && !isRecoverableError) {
161
+ const shouldShowToolError = shouldShowToolErrorWarning({
162
+ lastToolError: params.lastToolError,
163
+ hasUserFacingReply: hasUserFacingAssistantReply,
164
+ suppressToolErrors: Boolean(params.config?.messages?.suppressToolErrors),
165
+ suppressToolErrorWarnings: params.suppressToolErrorWarnings,
166
+ });
167
+ // Always surface mutating tool failures so we do not silently confirm actions that did not happen.
168
+ // Otherwise, keep the previous behavior and only surface non-recoverable failures when no reply exists.
169
+ if (shouldShowToolError) {
135
170
  const toolSummary = formatToolAggregate(params.lastToolError.toolName, params.lastToolError.meta ? [params.lastToolError.meta] : undefined, { markdown: useMarkdown });
136
171
  const errorSuffix = params.lastToolError.error ? `: ${params.lastToolError.error}` : "";
137
- replyItems.push({
138
- text: `⚠️ ${toolSummary} failed${errorSuffix}`,
139
- isError: true,
140
- });
172
+ const warningText = `⚠️ ${toolSummary} failed${errorSuffix}`;
173
+ const normalizedWarning = normalizeTextForComparison(warningText);
174
+ const duplicateWarning = normalizedWarning
175
+ ? replyItems.some((item) => {
176
+ if (!item.text) {
177
+ return false;
178
+ }
179
+ const normalizedExisting = normalizeTextForComparison(item.text);
180
+ return normalizedExisting.length > 0 && normalizedExisting === normalizedWarning;
181
+ })
182
+ : false;
183
+ if (!duplicateWarning) {
184
+ replyItems.push({
185
+ text: warningText,
186
+ isError: true,
187
+ });
188
+ }
141
189
  }
142
190
  }
143
191
  const hasAudioAsVoiceTag = replyItems.some((item) => item.audioAsVoice);
@@ -153,10 +201,12 @@ export function buildEmbeddedRunPayloads(params) {
153
201
  audioAsVoice: item.audioAsVoice || Boolean(hasAudioAsVoiceTag && item.media?.length),
154
202
  }))
155
203
  .filter((p) => {
156
- if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0))
204
+ if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0)) {
157
205
  return false;
158
- if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN))
206
+ }
207
+ if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) {
159
208
  return false;
209
+ }
160
210
  return true;
161
211
  });
162
212
  }