@poolzin/pool-bot 2026.2.21 → 2026.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/device-pair/index.ts +2 -2
  326. package/extensions/diagnostics-otel/package.json +1 -1
  327. package/extensions/discord/package.json +1 -1
  328. package/extensions/feishu/package.json +1 -1
  329. package/extensions/google-antigravity-auth/package.json +1 -1
  330. package/extensions/google-gemini-cli-auth/package.json +1 -1
  331. package/extensions/googlechat/package.json +1 -1
  332. package/extensions/imessage/package.json +1 -1
  333. package/extensions/irc/package.json +1 -1
  334. package/extensions/irc/src/accounts.ts +1 -1
  335. package/extensions/irc/src/onboarding.ts +4 -4
  336. package/extensions/line/package.json +1 -1
  337. package/extensions/llm-task/package.json +1 -1
  338. package/extensions/lobster/package.json +1 -1
  339. package/extensions/matrix/CHANGELOG.md +10 -0
  340. package/extensions/matrix/package.json +1 -1
  341. package/extensions/mattermost/package.json +1 -1
  342. package/extensions/memory-core/package.json +1 -1
  343. package/extensions/memory-lancedb/package.json +1 -1
  344. package/extensions/minimax-portal-auth/package.json +1 -1
  345. package/extensions/msteams/CHANGELOG.md +10 -0
  346. package/extensions/msteams/package.json +1 -1
  347. package/extensions/nextcloud-talk/package.json +1 -1
  348. package/extensions/nostr/CHANGELOG.md +10 -0
  349. package/extensions/nostr/package.json +1 -1
  350. package/extensions/open-prose/package.json +1 -1
  351. package/extensions/openai-codex-auth/package.json +1 -1
  352. package/extensions/signal/package.json +1 -1
  353. package/extensions/slack/package.json +1 -1
  354. package/extensions/telegram/package.json +1 -1
  355. package/extensions/tlon/package.json +1 -1
  356. package/extensions/twitch/CHANGELOG.md +10 -0
  357. package/extensions/twitch/package.json +1 -1
  358. package/extensions/voice-call/CHANGELOG.md +10 -0
  359. package/extensions/voice-call/package.json +1 -1
  360. package/extensions/whatsapp/package.json +1 -1
  361. package/extensions/zalo/CHANGELOG.md +10 -0
  362. package/extensions/zalo/package.json +1 -1
  363. package/extensions/zalouser/CHANGELOG.md +10 -0
  364. package/extensions/zalouser/package.json +1 -1
  365. package/package.json +1 -1
  366. package/skills/apple-reminders/SKILL.md +100 -49
  367. package/skills/coding-agent/SKILL.md +34 -28
  368. package/skills/github/SKILL.md +131 -16
  369. package/skills/imsg/SKILL.md +112 -15
  370. package/skills/openhue/SKILL.md +101 -19
  371. package/skills/tmux/SKILL.md +111 -79
  372. package/skills/weather/SKILL.md +88 -25
  373. package/dist/agents/openclaw-tools.js +0 -151
  374. package/dist/agents/tool-security.js +0 -96
  375. package/dist/gateway/url-validation.js +0 -94
  376. package/dist/infra/openclaw-root.js +0 -109
  377. package/dist/infra/tmp-openclaw-dir.js +0 -81
  378. package/dist/media/path-sanitization.js +0 -78
@@ -1,71 +1,121 @@
1
+ import crypto from "node:crypto";
1
2
  import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { acquireSessionWriteLock } from "../session-write-lock.js";
2
5
  import { SANDBOX_BROWSER_REGISTRY_PATH, SANDBOX_REGISTRY_PATH, SANDBOX_STATE_DIR, } from "./constants.js";
3
- export async function readRegistry() {
6
+ function isRecord(value) {
7
+ return Boolean(value) && typeof value === "object";
8
+ }
9
+ function isRegistryEntry(value) {
10
+ return isRecord(value) && typeof value.containerName === "string";
11
+ }
12
+ function isRegistryFile(value) {
13
+ if (!isRecord(value)) {
14
+ return false;
15
+ }
16
+ const maybeEntries = value.entries;
17
+ return Array.isArray(maybeEntries) && maybeEntries.every(isRegistryEntry);
18
+ }
19
+ async function withRegistryLock(registryPath, fn) {
20
+ const lock = await acquireSessionWriteLock({ sessionFile: registryPath, allowReentrant: false });
21
+ try {
22
+ return await fn();
23
+ }
24
+ finally {
25
+ await lock.release();
26
+ }
27
+ }
28
+ async function readRegistryFromFile(registryPath, mode) {
4
29
  try {
5
- const raw = await fs.readFile(SANDBOX_REGISTRY_PATH, "utf-8");
30
+ const raw = await fs.readFile(registryPath, "utf-8");
6
31
  const parsed = JSON.parse(raw);
7
- if (parsed && Array.isArray(parsed.entries))
32
+ if (isRegistryFile(parsed)) {
8
33
  return parsed;
34
+ }
35
+ if (mode === "fallback") {
36
+ return { entries: [] };
37
+ }
38
+ throw new Error(`Invalid sandbox registry format: ${registryPath}`);
9
39
  }
10
- catch {
11
- // ignore
40
+ catch (error) {
41
+ const code = error?.code;
42
+ if (code === "ENOENT") {
43
+ return { entries: [] };
44
+ }
45
+ if (mode === "fallback") {
46
+ return { entries: [] };
47
+ }
48
+ if (error instanceof Error) {
49
+ throw error;
50
+ }
51
+ throw new Error(`Failed to read sandbox registry file: ${registryPath}`, { cause: error });
12
52
  }
13
- return { entries: [] };
14
53
  }
15
- async function writeRegistry(registry) {
54
+ async function writeRegistryFile(registryPath, registry) {
16
55
  await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
17
- await fs.writeFile(SANDBOX_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
56
+ const payload = `${JSON.stringify(registry, null, 2)}\n`;
57
+ const registryDir = path.dirname(registryPath);
58
+ const tempPath = path.join(registryDir, `${path.basename(registryPath)}.${crypto.randomUUID()}.tmp`);
59
+ await fs.writeFile(tempPath, payload, "utf-8");
60
+ try {
61
+ await fs.rename(tempPath, registryPath);
62
+ }
63
+ catch (error) {
64
+ await fs.rm(tempPath, { force: true });
65
+ throw error;
66
+ }
18
67
  }
19
- export async function updateRegistry(entry) {
20
- const registry = await readRegistry();
21
- const existing = registry.entries.find((item) => item.containerName === entry.containerName);
22
- const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
68
+ export async function readRegistry() {
69
+ return await readRegistryFromFile(SANDBOX_REGISTRY_PATH, "fallback");
70
+ }
71
+ function upsertEntry(entries, entry) {
72
+ const existing = entries.find((item) => item.containerName === entry.containerName);
73
+ const next = entries.filter((item) => item.containerName !== entry.containerName);
23
74
  next.push({
24
75
  ...entry,
25
76
  createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
26
77
  image: existing?.image ?? entry.image,
27
78
  configHash: entry.configHash ?? existing?.configHash,
28
79
  });
29
- await writeRegistry({ entries: next });
80
+ return next;
81
+ }
82
+ function removeEntry(entries, containerName) {
83
+ return entries.filter((entry) => entry.containerName !== containerName);
84
+ }
85
+ async function withRegistryMutation(registryPath, mutate) {
86
+ await withRegistryLock(registryPath, async () => {
87
+ const registry = await readRegistryFromFile(registryPath, "strict");
88
+ const next = mutate(registry.entries);
89
+ if (next === null) {
90
+ return;
91
+ }
92
+ await writeRegistryFile(registryPath, { entries: next });
93
+ });
94
+ }
95
+ export async function updateRegistry(entry) {
96
+ await withRegistryMutation(SANDBOX_REGISTRY_PATH, (entries) => upsertEntry(entries, entry));
30
97
  }
31
98
  export async function removeRegistryEntry(containerName) {
32
- const registry = await readRegistry();
33
- const next = registry.entries.filter((item) => item.containerName !== containerName);
34
- if (next.length === registry.entries.length)
35
- return;
36
- await writeRegistry({ entries: next });
99
+ await withRegistryMutation(SANDBOX_REGISTRY_PATH, (entries) => {
100
+ const next = removeEntry(entries, containerName);
101
+ if (next.length === entries.length) {
102
+ return null;
103
+ }
104
+ return next;
105
+ });
37
106
  }
38
107
  export async function readBrowserRegistry() {
39
- try {
40
- const raw = await fs.readFile(SANDBOX_BROWSER_REGISTRY_PATH, "utf-8");
41
- const parsed = JSON.parse(raw);
42
- if (parsed && Array.isArray(parsed.entries))
43
- return parsed;
44
- }
45
- catch {
46
- // ignore
47
- }
48
- return { entries: [] };
49
- }
50
- async function writeBrowserRegistry(registry) {
51
- await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
52
- await fs.writeFile(SANDBOX_BROWSER_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
108
+ return await readRegistryFromFile(SANDBOX_BROWSER_REGISTRY_PATH, "fallback");
53
109
  }
54
110
  export async function updateBrowserRegistry(entry) {
55
- const registry = await readBrowserRegistry();
56
- const existing = registry.entries.find((item) => item.containerName === entry.containerName);
57
- const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
58
- next.push({
59
- ...entry,
60
- createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
61
- image: existing?.image ?? entry.image,
62
- });
63
- await writeBrowserRegistry({ entries: next });
111
+ await withRegistryMutation(SANDBOX_BROWSER_REGISTRY_PATH, (entries) => upsertEntry(entries, entry));
64
112
  }
65
113
  export async function removeBrowserRegistryEntry(containerName) {
66
- const registry = await readBrowserRegistry();
67
- const next = registry.entries.filter((item) => item.containerName !== containerName);
68
- if (next.length === registry.entries.length)
69
- return;
70
- await writeBrowserRegistry({ entries: next });
114
+ await withRegistryMutation(SANDBOX_BROWSER_REGISTRY_PATH, (entries) => {
115
+ const next = removeEntry(entries, containerName);
116
+ if (next.length === entries.length) {
117
+ return null;
118
+ }
119
+ return next;
120
+ });
71
121
  }
@@ -0,0 +1,82 @@
1
+ const BLOCKED_ENV_VAR_PATTERNS = [
2
+ /^ANTHROPIC_API_KEY$/i,
3
+ /^OPENAI_API_KEY$/i,
4
+ /^GEMINI_API_KEY$/i,
5
+ /^OPENROUTER_API_KEY$/i,
6
+ /^MINIMAX_API_KEY$/i,
7
+ /^ELEVENLABS_API_KEY$/i,
8
+ /^SYNTHETIC_API_KEY$/i,
9
+ /^TELEGRAM_BOT_TOKEN$/i,
10
+ /^DISCORD_BOT_TOKEN$/i,
11
+ /^SLACK_(BOT|APP)_TOKEN$/i,
12
+ /^LINE_CHANNEL_SECRET$/i,
13
+ /^LINE_CHANNEL_ACCESS_TOKEN$/i,
14
+ /^POOLBOT_GATEWAY_(TOKEN|PASSWORD)$/i,
15
+ /^AWS_(SECRET_ACCESS_KEY|SECRET_KEY|SESSION_TOKEN)$/i,
16
+ /^(GH|GITHUB)_TOKEN$/i,
17
+ /^(AZURE|AZURE_OPENAI|COHERE|AI_GATEWAY|OPENROUTER)_API_KEY$/i,
18
+ /_?(API_KEY|TOKEN|PASSWORD|PRIVATE_KEY|SECRET)$/i,
19
+ ];
20
+ const ALLOWED_ENV_VAR_PATTERNS = [
21
+ /^LANG$/,
22
+ /^LC_.*$/i,
23
+ /^PATH$/i,
24
+ /^HOME$/i,
25
+ /^USER$/i,
26
+ /^SHELL$/i,
27
+ /^TERM$/i,
28
+ /^TZ$/i,
29
+ /^NODE_ENV$/i,
30
+ ];
31
+ function validateEnvVarValue(value) {
32
+ if (value.includes("\0")) {
33
+ return "Contains null bytes";
34
+ }
35
+ if (value.length > 32768) {
36
+ return "Value exceeds maximum length";
37
+ }
38
+ if (/^[A-Za-z0-9+/=]{80,}$/.test(value)) {
39
+ return "Value looks like base64-encoded credential data";
40
+ }
41
+ return undefined;
42
+ }
43
+ function matchesAnyPattern(value, patterns) {
44
+ return patterns.some((pattern) => pattern.test(value));
45
+ }
46
+ export function sanitizeEnvVars(envVars, options = {}) {
47
+ const allowed = {};
48
+ const blocked = [];
49
+ const warnings = [];
50
+ const blockedPatterns = [...BLOCKED_ENV_VAR_PATTERNS, ...(options.customBlockedPatterns ?? [])];
51
+ const allowedPatterns = [...ALLOWED_ENV_VAR_PATTERNS, ...(options.customAllowedPatterns ?? [])];
52
+ for (const [rawKey, value] of Object.entries(envVars)) {
53
+ const key = rawKey.trim();
54
+ if (!key) {
55
+ continue;
56
+ }
57
+ if (matchesAnyPattern(key, blockedPatterns)) {
58
+ blocked.push(key);
59
+ continue;
60
+ }
61
+ if (options.strictMode && !matchesAnyPattern(key, allowedPatterns)) {
62
+ blocked.push(key);
63
+ continue;
64
+ }
65
+ const warning = validateEnvVarValue(value);
66
+ if (warning) {
67
+ if (warning === "Contains null bytes") {
68
+ blocked.push(key);
69
+ continue;
70
+ }
71
+ warnings.push(`${key}: ${warning}`);
72
+ }
73
+ allowed[key] = value;
74
+ }
75
+ return { allowed, blocked, warnings };
76
+ }
77
+ export function getBlockedPatterns() {
78
+ return BLOCKED_ENV_VAR_PATTERNS.map((pattern) => pattern.source);
79
+ }
80
+ export function getAllowedPatterns() {
81
+ return ALLOWED_ENV_VAR_PATTERNS.map((pattern) => pattern.source);
82
+ }
@@ -2,9 +2,9 @@ import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
5
6
  const HTTP_URL_RE = /^https?:\/\//i;
6
7
  const DATA_URL_RE = /^data:/i;
7
- const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
8
8
  function normalizeUnicodeSpaces(str) {
9
9
  return str.replace(UNICODE_SPACES, " ");
10
10
  }
@@ -20,15 +20,16 @@ function expandPath(filePath) {
20
20
  }
21
21
  function resolveToCwd(filePath, cwd) {
22
22
  const expanded = expandPath(filePath);
23
- if (path.isAbsolute(expanded))
23
+ if (path.isAbsolute(expanded)) {
24
24
  return expanded;
25
+ }
25
26
  return path.resolve(cwd, expanded);
26
27
  }
27
28
  export function resolveSandboxInputPath(filePath, cwd) {
28
29
  return resolveToCwd(filePath, cwd);
29
30
  }
30
31
  export function resolveSandboxPath(params) {
31
- const resolved = resolveToCwd(params.filePath, params.cwd);
32
+ const resolved = resolveSandboxInputPath(params.filePath, params.cwd);
32
33
  const rootResolved = path.resolve(params.root);
33
34
  const relative = path.relative(rootResolved, resolved);
34
35
  if (!relative || relative === "") {
@@ -41,7 +42,9 @@ export function resolveSandboxPath(params) {
41
42
  }
42
43
  export async function assertSandboxPath(params) {
43
44
  const resolved = resolveSandboxPath(params);
44
- await assertNoSymlink(resolved.relative, path.resolve(params.root));
45
+ await assertNoSymlinkEscape(resolved.relative, path.resolve(params.root), {
46
+ allowFinalSymlink: params.allowFinalSymlink,
47
+ });
45
48
  return resolved;
46
49
  }
47
50
  export function assertMediaNotDataUrl(media) {
@@ -52,10 +55,12 @@ export function assertMediaNotDataUrl(media) {
52
55
  }
53
56
  export async function resolveSandboxedMediaSource(params) {
54
57
  const raw = params.media.trim();
55
- if (!raw)
58
+ if (!raw) {
56
59
  return raw;
57
- if (HTTP_URL_RE.test(raw))
60
+ }
61
+ if (HTTP_URL_RE.test(raw)) {
58
62
  return raw;
63
+ }
59
64
  let candidate = raw;
60
65
  if (/^file:\/\//i.test(candidate)) {
61
66
  try {
@@ -72,17 +77,30 @@ export async function resolveSandboxedMediaSource(params) {
72
77
  });
73
78
  return resolved.resolved;
74
79
  }
75
- async function assertNoSymlink(relative, root) {
76
- if (!relative)
80
+ async function assertNoSymlinkEscape(relative, root, options) {
81
+ if (!relative) {
77
82
  return;
83
+ }
84
+ const rootReal = await tryRealpath(root);
78
85
  const parts = relative.split(path.sep).filter(Boolean);
79
86
  let current = root;
80
- for (const part of parts) {
87
+ for (let idx = 0; idx < parts.length; idx += 1) {
88
+ const part = parts[idx];
89
+ const isLast = idx === parts.length - 1;
81
90
  current = path.join(current, part);
82
91
  try {
83
92
  const stat = await fs.lstat(current);
84
93
  if (stat.isSymbolicLink()) {
85
- throw new Error(`Symlink not allowed in sandbox path: ${current}`);
94
+ // Unlinking a symlink itself is safe even if it points outside the root. What we
95
+ // must prevent is traversing through a symlink to reach targets outside root.
96
+ if (options?.allowFinalSymlink && isLast) {
97
+ return;
98
+ }
99
+ const target = await tryRealpath(current);
100
+ if (!isPathInside(rootReal, target)) {
101
+ throw new Error(`Symlink escapes sandbox root (${shortPath(rootReal)}): ${shortPath(current)}`);
102
+ }
103
+ current = target;
86
104
  }
87
105
  }
88
106
  catch (err) {
@@ -94,6 +112,21 @@ async function assertNoSymlink(relative, root) {
94
112
  }
95
113
  }
96
114
  }
115
+ async function tryRealpath(value) {
116
+ try {
117
+ return await fs.realpath(value);
118
+ }
119
+ catch {
120
+ return path.resolve(value);
121
+ }
122
+ }
123
+ function isPathInside(root, target) {
124
+ const relative = path.relative(root, target);
125
+ if (!relative || relative === "") {
126
+ return true;
127
+ }
128
+ return !(relative.startsWith("..") || path.isAbsolute(relative));
129
+ }
97
130
  function shortPath(value) {
98
131
  if (value.startsWith(os.homedir())) {
99
132
  return `~${value.slice(os.homedir().length)}`;
@@ -1,4 +1,5 @@
1
1
  import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
2
+ import { applyInputProvenanceToUserMessage, } from "../sessions/input-provenance.js";
2
3
  import { installSessionToolResultGuard } from "./session-tool-result-guard.js";
3
4
  /**
4
5
  * Apply the tool-result guard to a SessionManager exactly once and expose
@@ -9,25 +10,36 @@ export function guardSessionManager(sessionManager, opts) {
9
10
  return sessionManager;
10
11
  }
11
12
  const hookRunner = getGlobalHookRunner();
12
- const transform = hookRunner?.hasHooks("tool_result_persist")
13
- ? (message, meta) => {
14
- const out = hookRunner.runToolResultPersist({
15
- toolName: meta.toolName,
16
- toolCallId: meta.toolCallId,
17
- message,
18
- isSynthetic: meta.isSynthetic,
19
- }, {
13
+ const beforeMessageWrite = hookRunner?.hasHooks("before_message_write")
14
+ ? (event) => {
15
+ return hookRunner.runBeforeMessageWrite(event, {
20
16
  agentId: opts?.agentId,
21
17
  sessionKey: opts?.sessionKey,
22
- toolName: meta.toolName,
23
- toolCallId: meta.toolCallId,
24
18
  });
25
- return out?.message ?? message;
26
19
  }
27
20
  : undefined;
21
+ const transform = hookRunner?.hasHooks("tool_result_persist")
22
+ ? // oxlint-disable-next-line typescript/no-explicit-any
23
+ (message, meta) => {
24
+ const out = hookRunner.runToolResultPersist({
25
+ toolName: meta.toolName,
26
+ toolCallId: meta.toolCallId,
27
+ message,
28
+ isSynthetic: meta.isSynthetic,
29
+ }, {
30
+ agentId: opts?.agentId,
31
+ sessionKey: opts?.sessionKey,
32
+ toolName: meta.toolName,
33
+ toolCallId: meta.toolCallId,
34
+ });
35
+ return out?.message ?? message;
36
+ }
37
+ : undefined;
28
38
  const guard = installSessionToolResultGuard(sessionManager, {
39
+ transformMessageForPersistence: (message) => applyInputProvenanceToUserMessage(message, opts?.inputProvenance),
29
40
  transformToolResultForPersistence: transform,
30
41
  allowSyntheticToolResults: opts?.allowSyntheticToolResults,
42
+ beforeMessageWriteHook: beforeMessageWrite,
31
43
  });
32
44
  sessionManager.flushPendingToolResults = guard.flushPendingToolResults;
33
45
  return sessionManager;
@@ -1,6 +1,7 @@
1
1
  import { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js";
2
2
  import { HARD_MAX_TOOL_RESULT_CHARS } from "./pi-embedded-runner/tool-result-truncation.js";
3
3
  import { makeMissingToolResult, sanitizeToolCallInputs } from "./session-transcript-repair.js";
4
+ import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
4
5
  const GUARD_TRUNCATION_SUFFIX = "\n\n⚠️ [Content truncated during persistence — original exceeded size limit. " +
5
6
  "Use offset/limit parameters or request specific sections for large content.]";
6
7
  /**
@@ -57,48 +58,36 @@ function capToolResultSize(msg) {
57
58
  });
58
59
  return { ...msg, content: newContent };
59
60
  }
60
- function extractAssistantToolCalls(msg) {
61
- const content = msg.content;
62
- if (!Array.isArray(content)) {
63
- return [];
64
- }
65
- const toolCalls = [];
66
- for (const block of content) {
67
- if (!block || typeof block !== "object") {
68
- continue;
69
- }
70
- const rec = block;
71
- if (typeof rec.id !== "string" || !rec.id) {
72
- continue;
73
- }
74
- if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
75
- toolCalls.push({
76
- id: rec.id,
77
- name: typeof rec.name === "string" ? rec.name : undefined,
78
- });
79
- }
80
- }
81
- return toolCalls;
82
- }
83
- function extractToolResultId(msg) {
84
- const toolCallId = msg.toolCallId;
85
- if (typeof toolCallId === "string" && toolCallId) {
86
- return toolCallId;
87
- }
88
- const toolUseId = msg.toolUseId;
89
- if (typeof toolUseId === "string" && toolUseId) {
90
- return toolUseId;
91
- }
92
- return null;
93
- }
94
61
  export function installSessionToolResultGuard(sessionManager, opts) {
95
62
  const originalAppend = sessionManager.appendMessage.bind(sessionManager);
96
63
  const pending = new Map();
64
+ const persistMessage = (message) => {
65
+ const transformer = opts?.transformMessageForPersistence;
66
+ return transformer ? transformer(message) : message;
67
+ };
97
68
  const persistToolResult = (message, meta) => {
98
69
  const transformer = opts?.transformToolResultForPersistence;
99
70
  return transformer ? transformer(message, meta) : message;
100
71
  };
101
72
  const allowSyntheticToolResults = opts?.allowSyntheticToolResults ?? true;
73
+ const beforeWrite = opts?.beforeMessageWriteHook;
74
+ /**
75
+ * Run the before_message_write hook. Returns the (possibly modified) message,
76
+ * or null if the message should be blocked.
77
+ */
78
+ const applyBeforeWriteHook = (msg) => {
79
+ if (!beforeWrite) {
80
+ return msg;
81
+ }
82
+ const result = beforeWrite({ message: msg });
83
+ if (result?.block) {
84
+ return null;
85
+ }
86
+ if (result?.message) {
87
+ return result.message;
88
+ }
89
+ return msg;
90
+ };
102
91
  const flushPendingToolResults = () => {
103
92
  if (pending.size === 0) {
104
93
  return;
@@ -106,11 +95,14 @@ export function installSessionToolResultGuard(sessionManager, opts) {
106
95
  if (allowSyntheticToolResults) {
107
96
  for (const [id, name] of pending.entries()) {
108
97
  const synthetic = makeMissingToolResult({ toolCallId: id, toolName: name });
109
- originalAppend(persistToolResult(synthetic, {
98
+ const flushed = applyBeforeWriteHook(persistToolResult(persistMessage(synthetic), {
110
99
  toolCallId: id,
111
100
  toolName: name,
112
101
  isSynthetic: true,
113
102
  }));
103
+ if (flushed) {
104
+ originalAppend(flushed);
105
+ }
114
106
  }
115
107
  }
116
108
  pending.clear();
@@ -137,15 +129,19 @@ export function installSessionToolResultGuard(sessionManager, opts) {
137
129
  }
138
130
  // Apply hard size cap before persistence to prevent oversized tool results
139
131
  // from consuming the entire context window on subsequent LLM calls.
140
- const capped = capToolResultSize(nextMessage);
141
- return originalAppend(persistToolResult(capped, {
132
+ const capped = capToolResultSize(persistMessage(nextMessage));
133
+ const persisted = applyBeforeWriteHook(persistToolResult(capped, {
142
134
  toolCallId: id ?? undefined,
143
135
  toolName,
144
136
  isSynthetic: false,
145
137
  }));
138
+ if (!persisted) {
139
+ return undefined;
140
+ }
141
+ return originalAppend(persisted);
146
142
  }
147
143
  const toolCalls = nextRole === "assistant"
148
- ? extractAssistantToolCalls(nextMessage)
144
+ ? extractToolCallsFromAssistant(nextMessage)
149
145
  : [];
150
146
  if (allowSyntheticToolResults) {
151
147
  // If previous tool calls are still pending, flush before non-tool results.
@@ -157,7 +153,11 @@ export function installSessionToolResultGuard(sessionManager, opts) {
157
153
  flushPendingToolResults();
158
154
  }
159
155
  }
160
- const result = originalAppend(nextMessage);
156
+ const finalMessage = applyBeforeWriteHook(persistMessage(nextMessage));
157
+ if (!finalMessage) {
158
+ return undefined;
159
+ }
160
+ const result = originalAppend(finalMessage);
161
161
  const sessionFile = sessionManager.getSessionFile?.();
162
162
  if (sessionFile) {
163
163
  emitSessionTranscriptUpdate(sessionFile);
@@ -1,44 +1,25 @@
1
- const TOOL_CALL_TYPES = new Set(["toolCall", "toolUse", "functionCall"]);
2
- function extractToolCallsFromAssistant(msg) {
3
- const content = msg.content;
4
- if (!Array.isArray(content))
5
- return [];
6
- const toolCalls = [];
7
- for (const block of content) {
8
- if (!block || typeof block !== "object")
9
- continue;
10
- const rec = block;
11
- if (typeof rec.id !== "string" || !rec.id)
12
- continue;
13
- if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
14
- toolCalls.push({
15
- id: rec.id,
16
- name: typeof rec.name === "string" ? rec.name : undefined,
17
- });
18
- }
19
- }
20
- return toolCalls;
21
- }
1
+ import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
22
2
  function isToolCallBlock(block) {
23
3
  if (!block || typeof block !== "object") {
24
4
  return false;
25
5
  }
26
6
  const type = block.type;
27
- return typeof type === "string" && TOOL_CALL_TYPES.has(type);
7
+ return (typeof type === "string" &&
8
+ (type === "toolCall" || type === "toolUse" || type === "functionCall"));
28
9
  }
29
10
  function hasToolCallInput(block) {
30
11
  const hasInput = "input" in block ? block.input !== undefined && block.input !== null : false;
31
12
  const hasArguments = "arguments" in block ? block.arguments !== undefined && block.arguments !== null : false;
32
13
  return hasInput || hasArguments;
33
14
  }
34
- function extractToolResultId(msg) {
35
- const toolCallId = msg.toolCallId;
36
- if (typeof toolCallId === "string" && toolCallId)
37
- return toolCallId;
38
- const toolUseId = msg.toolUseId;
39
- if (typeof toolUseId === "string" && toolUseId)
40
- return toolUseId;
41
- return null;
15
+ function hasNonEmptyStringField(value) {
16
+ return typeof value === "string" && value.trim().length > 0;
17
+ }
18
+ function hasToolCallId(block) {
19
+ return hasNonEmptyStringField(block.id);
20
+ }
21
+ function hasToolCallName(block) {
22
+ return hasNonEmptyStringField(block.name);
42
23
  }
43
24
  function makeMissingToolResult(params) {
44
25
  return {
@@ -56,6 +37,24 @@ function makeMissingToolResult(params) {
56
37
  };
57
38
  }
58
39
  export { makeMissingToolResult };
40
+ export function stripToolResultDetails(messages) {
41
+ let touched = false;
42
+ const out = [];
43
+ for (const msg of messages) {
44
+ if (!msg || typeof msg !== "object" || msg.role !== "toolResult") {
45
+ out.push(msg);
46
+ continue;
47
+ }
48
+ if (!("details" in msg)) {
49
+ out.push(msg);
50
+ continue;
51
+ }
52
+ const { details: _details, ...rest } = msg;
53
+ touched = true;
54
+ out.push(rest);
55
+ }
56
+ return touched ? out : messages;
57
+ }
59
58
  export function repairToolCallInputs(messages) {
60
59
  let droppedToolCalls = 0;
61
60
  let droppedAssistantMessages = 0;
@@ -73,7 +72,8 @@ export function repairToolCallInputs(messages) {
73
72
  const nextContent = [];
74
73
  let droppedInMessage = 0;
75
74
  for (const block of msg.content) {
76
- if (isToolCallBlock(block) && !hasToolCallInput(block)) {
75
+ if (isToolCallBlock(block) &&
76
+ (!hasToolCallInput(block) || !hasToolCallId(block) || !hasToolCallName(block))) {
77
77
  droppedToolCalls += 1;
78
78
  droppedInMessage += 1;
79
79
  changed = true;
@@ -125,8 +125,9 @@ export function repairToolUseResultPairing(messages) {
125
125
  changed = true;
126
126
  return;
127
127
  }
128
- if (id)
128
+ if (id) {
129
129
  seenToolResultIds.add(id);
130
+ }
130
131
  out.push(msg);
131
132
  };
132
133
  for (let i = 0; i < messages.length; i += 1) {
@@ -155,6 +156,7 @@ export function repairToolUseResultPairing(messages) {
155
156
  // (e.g., partialJson: true) and should not have synthetic tool_results created.
156
157
  // Creating synthetic results for incomplete tool calls causes API 400 errors:
157
158
  // "unexpected tool_use_id found in tool_result blocks"
159
+ // See: https://github.com/poolbot/poolbot/issues/4597
158
160
  const stopReason = assistant.stopReason;
159
161
  if (stopReason === "error" || stopReason === "aborted") {
160
162
  out.push(msg);
@@ -176,8 +178,9 @@ export function repairToolUseResultPairing(messages) {
176
178
  continue;
177
179
  }
178
180
  const nextRole = next.role;
179
- if (nextRole === "assistant")
181
+ if (nextRole === "assistant") {
180
182
  break;
183
+ }
181
184
  if (nextRole === "toolResult") {
182
185
  const toolResult = next;
183
186
  const id = extractToolResultId(toolResult);