@poolzin/pool-bot 2026.2.20 → 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 (388) 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-auth.js +12 -0
  14. package/dist/agents/model-catalog.js +40 -9
  15. package/dist/agents/model-fallback.js +24 -0
  16. package/dist/agents/model-forward-compat.js +60 -23
  17. package/dist/agents/model-selection.js +134 -41
  18. package/dist/agents/pi-auth-json.js +2 -2
  19. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  20. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  21. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  22. package/dist/agents/pi-embedded-helpers.js +2 -2
  23. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  24. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  25. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  26. package/dist/agents/pi-embedded-runner/google.js +109 -19
  27. package/dist/agents/pi-embedded-runner/history.js +35 -17
  28. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -80
  29. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  30. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  31. package/dist/agents/pi-embedded-runner/run.js +193 -25
  32. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  33. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  34. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  35. package/dist/agents/pi-embedded-runner.js +1 -1
  36. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  37. package/dist/agents/pi-embedded-subscribe.js +37 -0
  38. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  39. package/dist/agents/pi-model-discovery.js +9 -2
  40. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  41. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  42. package/dist/agents/pi-tools.js +113 -94
  43. package/dist/agents/pi-tools.read.js +337 -38
  44. package/dist/agents/poolbot-tools.js +14 -5
  45. package/dist/agents/provider/config-loader.js +76 -0
  46. package/dist/agents/provider/index.js +15 -0
  47. package/dist/agents/provider/integration.js +136 -0
  48. package/dist/agents/provider/models-dev.js +129 -0
  49. package/dist/agents/provider/rate-limits.js +458 -0
  50. package/dist/agents/provider/request-monitor.js +449 -0
  51. package/dist/agents/provider/session-binding.js +376 -0
  52. package/dist/agents/provider/token-pool.js +541 -0
  53. package/dist/agents/sandbox/docker.js +10 -5
  54. package/dist/agents/sandbox/registry.js +96 -46
  55. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  56. package/dist/agents/sandbox-paths.js +43 -10
  57. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  58. package/dist/agents/session-tool-result-guard.js +39 -39
  59. package/dist/agents/session-transcript-repair.js +36 -33
  60. package/dist/agents/session-write-lock.js +62 -44
  61. package/dist/agents/skills/frontmatter.js +49 -88
  62. package/dist/agents/skills/workspace.js +335 -28
  63. package/dist/agents/subagent-announce.js +508 -174
  64. package/dist/agents/subagent-registry.js +45 -4
  65. package/dist/agents/subagent-spawn.js +16 -33
  66. package/dist/agents/system-prompt-report.js +27 -10
  67. package/dist/agents/system-prompt.js +26 -32
  68. package/dist/agents/tool-call-id.js +69 -17
  69. package/dist/agents/tool-display-common.js +1 -1
  70. package/dist/agents/tool-images.js +64 -31
  71. package/dist/agents/tools/canvas-tool.js +17 -11
  72. package/dist/agents/tools/common.js +37 -19
  73. package/dist/agents/tools/cron-tool.js +40 -38
  74. package/dist/agents/tools/gateway.js +70 -2
  75. package/dist/agents/tools/message-tool.js +181 -40
  76. package/dist/agents/tools/nodes-tool.js +128 -36
  77. package/dist/agents/tools/nodes-utils.js +12 -38
  78. package/dist/agents/tools/session-status-tool.js +24 -71
  79. package/dist/agents/tools/sessions-helpers.js +38 -210
  80. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  81. package/dist/agents/tools/telegram-actions.js +58 -7
  82. package/dist/agents/tools/web-fetch-utils.js +112 -7
  83. package/dist/agents/tools/web-fetch.js +279 -175
  84. package/dist/agents/tools/web-shared.js +71 -8
  85. package/dist/agents/usage.js +25 -16
  86. package/dist/auto-reply/commands-registry.data.js +85 -11
  87. package/dist/auto-reply/dispatch.js +40 -21
  88. package/dist/auto-reply/reply/abort.js +102 -33
  89. package/dist/auto-reply/reply/commands-core.js +82 -33
  90. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  91. package/dist/auto-reply/reply/commands-info.js +41 -12
  92. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  93. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  94. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  95. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  96. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  97. package/dist/auto-reply/reply/mentions.js +18 -11
  98. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  99. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  100. package/dist/auto-reply/reply/session.js +102 -21
  101. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  102. package/dist/auto-reply/status.js +73 -50
  103. package/dist/browser/extension-relay.js +3 -3
  104. package/dist/browser/http-auth.js +1 -1
  105. package/dist/browser/paths.js +2 -2
  106. package/dist/build-info.json +3 -3
  107. package/dist/channels/allowlist-match.js +20 -0
  108. package/dist/channels/allowlists/resolve-utils.js +65 -2
  109. package/dist/channels/chat-type.js +8 -4
  110. package/dist/channels/dock.js +127 -35
  111. package/dist/channels/draft-stream-loop.js +6 -2
  112. package/dist/channels/plugins/actions/telegram.js +42 -18
  113. package/dist/channels/plugins/allowlist-match.js +1 -1
  114. package/dist/channels/plugins/group-mentions.js +51 -41
  115. package/dist/channels/plugins/message-action-names.js +2 -0
  116. package/dist/channels/plugins/message-actions.js +24 -5
  117. package/dist/channels/plugins/normalize/discord.js +26 -4
  118. package/dist/channels/plugins/normalize/signal.js +35 -22
  119. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  120. package/dist/channels/plugins/outbound/imessage.js +15 -14
  121. package/dist/channels/registry.js +20 -7
  122. package/dist/cli/acp-cli.js +7 -5
  123. package/dist/cli/browser-cli-extension.js +25 -12
  124. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  125. package/dist/cli/browser-cli-state.js +101 -145
  126. package/dist/cli/command-options.js +28 -0
  127. package/dist/cli/completion-cli.js +6 -6
  128. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  129. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  130. package/dist/cli/cron-cli/shared.js +7 -1
  131. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  132. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  133. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  134. package/dist/cli/daemon-cli.js +1 -0
  135. package/dist/cli/devices-cli.js +33 -20
  136. package/dist/cli/gateway-cli/register.js +37 -105
  137. package/dist/cli/gateway-cli/run.js +49 -11
  138. package/dist/cli/nodes-camera.js +59 -4
  139. package/dist/cli/nodes-cli/register.camera.js +27 -24
  140. package/dist/cli/nodes-cli/rpc.js +21 -38
  141. package/dist/cli/qr-cli.js +2 -2
  142. package/dist/cli/skills-cli.format.js +2 -2
  143. package/dist/cli/update-cli/progress.js +2 -2
  144. package/dist/cli/update-cli/restart-helper.js +28 -7
  145. package/dist/cli/update-cli/shared.js +7 -7
  146. package/dist/cli/update-cli/status.js +1 -1
  147. package/dist/cli/update-cli/update-command.js +14 -8
  148. package/dist/cli/update-cli/wizard.js +2 -2
  149. package/dist/cli/update-cli.js +21 -1027
  150. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  151. package/dist/commands/channels/add-mutators.js +3 -35
  152. package/dist/commands/channels/add.js +39 -51
  153. package/dist/commands/config-validation.js +1 -1
  154. package/dist/commands/configure.gateway-auth.js +52 -15
  155. package/dist/commands/configure.gateway.js +84 -40
  156. package/dist/commands/doctor-completion.js +3 -3
  157. package/dist/commands/doctor-config-flow.js +536 -16
  158. package/dist/commands/doctor-gateway-services.js +103 -79
  159. package/dist/commands/doctor-memory-search.js +9 -9
  160. package/dist/commands/doctor-platform-notes.js +57 -30
  161. package/dist/commands/doctor-prompter.js +26 -15
  162. package/dist/commands/doctor-session-locks.js +1 -1
  163. package/dist/commands/doctor.js +21 -9
  164. package/dist/commands/model-picker.js +120 -95
  165. package/dist/commands/models/set.js +2 -21
  166. package/dist/commands/models/shared.js +65 -37
  167. package/dist/commands/onboard-helpers.js +81 -39
  168. package/dist/commands/openai-codex-oauth.js +1 -1
  169. package/dist/commands/sessions.js +52 -53
  170. package/dist/commands/status.summary.js +52 -34
  171. package/dist/commands/test-wizard-helpers.js +2 -2
  172. package/dist/config/defaults.js +79 -42
  173. package/dist/config/group-policy.js +50 -18
  174. package/dist/config/includes.js +37 -10
  175. package/dist/config/schema.help.js +5 -4
  176. package/dist/config/schema.hints.js +2 -2
  177. package/dist/config/schema.labels.js +1 -0
  178. package/dist/config/sessions/group.js +12 -11
  179. package/dist/config/sessions/paths.js +137 -11
  180. package/dist/config/sessions/store.js +185 -65
  181. package/dist/config/sessions/types.js +15 -1
  182. package/dist/config/sessions.js +1 -0
  183. package/dist/config/telegram-custom-commands.js +3 -2
  184. package/dist/config/types.js +2 -0
  185. package/dist/config/zod-schema.agent-defaults.js +6 -27
  186. package/dist/config/zod-schema.agent-runtime.js +171 -79
  187. package/dist/config/zod-schema.providers-core.js +138 -65
  188. package/dist/config/zod-schema.session.js +49 -22
  189. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  190. package/dist/cron/isolated-agent/run.js +224 -57
  191. package/dist/cron/normalize.js +48 -45
  192. package/dist/cron/run-log.js +14 -0
  193. package/dist/cron/service/jobs.js +190 -28
  194. package/dist/cron/service/normalize.js +29 -11
  195. package/dist/cron/service/store.js +30 -44
  196. package/dist/cron/service/timer.js +182 -96
  197. package/dist/cron/service.js +3 -0
  198. package/dist/cron/stagger.js +37 -0
  199. package/dist/daemon/inspect.js +132 -92
  200. package/dist/daemon/runtime-paths.js +25 -4
  201. package/dist/daemon/service-audit.js +47 -16
  202. package/dist/discord/accounts.js +23 -20
  203. package/dist/discord/monitor/agent-components.js +1115 -219
  204. package/dist/discord/monitor/allow-list.js +114 -34
  205. package/dist/discord/monitor/listeners.js +204 -97
  206. package/dist/discord/monitor/message-handler.js +21 -10
  207. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  208. package/dist/discord/monitor/message-handler.process.js +384 -123
  209. package/dist/discord/monitor/message-utils.js +86 -23
  210. package/dist/discord/monitor/native-command.js +77 -57
  211. package/dist/discord/monitor/provider.js +122 -117
  212. package/dist/discord/monitor/reply-context.js +20 -16
  213. package/dist/discord/monitor/reply-delivery.js +40 -8
  214. package/dist/discord/monitor/rest-fetch.js +22 -0
  215. package/dist/discord/monitor/threading.js +117 -24
  216. package/dist/discord/send.js +2 -1
  217. package/dist/discord/send.outbound.js +124 -11
  218. package/dist/discord/send.shared.js +112 -72
  219. package/dist/discord/voice-message.js +3 -3
  220. package/dist/gateway/auth.js +119 -44
  221. package/dist/gateway/call.js +76 -34
  222. package/dist/gateway/channel-health-monitor.js +57 -50
  223. package/dist/gateway/client.js +63 -29
  224. package/dist/gateway/control-ui-contract.js +1 -1
  225. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  226. package/dist/gateway/net.js +109 -1
  227. package/dist/gateway/protocol/index.js +5 -8
  228. package/dist/gateway/protocol/schema/agent.js +19 -1
  229. package/dist/gateway/protocol/schema/channels.js +21 -0
  230. package/dist/gateway/protocol/schema/cron.js +43 -30
  231. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  232. package/dist/gateway/protocol/schema/sessions.js +5 -1
  233. package/dist/gateway/protocol/schema.js +0 -1
  234. package/dist/gateway/server/presence-events.js +12 -0
  235. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  236. package/dist/gateway/server/ws-connection.js +58 -21
  237. package/dist/gateway/server-broadcast.js +18 -13
  238. package/dist/gateway/server-cron.js +177 -10
  239. package/dist/gateway/server-methods/agent-job.js +131 -38
  240. package/dist/gateway/server-methods/send.js +60 -14
  241. package/dist/gateway/server-methods/sessions.js +160 -96
  242. package/dist/gateway/server-methods/system.js +5 -7
  243. package/dist/gateway/server-methods-list.js +8 -0
  244. package/dist/gateway/server-methods.js +24 -8
  245. package/dist/gateway/server-node-events.js +278 -68
  246. package/dist/gateway/session-utils.fs.js +316 -75
  247. package/dist/gateway/session-utils.js +224 -70
  248. package/dist/gateway/sessions-patch.js +63 -20
  249. package/dist/gateway/test-temp-config.js +1 -1
  250. package/dist/gateway/tools-invoke-http.js +118 -70
  251. package/dist/gateway/ws-log.js +135 -107
  252. package/dist/hooks/frontmatter.js +36 -82
  253. package/dist/hooks/install.js +149 -139
  254. package/dist/hooks/internal-hooks.js +29 -4
  255. package/dist/hooks/plugin-hooks.js +2 -1
  256. package/dist/imessage/monitor/deliver.js +10 -4
  257. package/dist/imessage/monitor/monitor-provider.js +138 -375
  258. package/dist/imessage/monitor/runtime.js +4 -8
  259. package/dist/imessage/send.js +65 -19
  260. package/dist/infra/exec-approvals-allowlist.js +7 -0
  261. package/dist/infra/exec-approvals.js +35 -920
  262. package/dist/infra/exec-safe-bin-trust.js +64 -0
  263. package/dist/infra/heartbeat-runner.js +207 -134
  264. package/dist/infra/heartbeat-wake.js +183 -22
  265. package/dist/infra/install-source-utils.js +47 -0
  266. package/dist/infra/net/ssrf.js +170 -36
  267. package/dist/infra/outbound/deliver.js +224 -58
  268. package/dist/infra/outbound/message-action-spec.js +12 -5
  269. package/dist/infra/outbound/outbound-session.js +27 -25
  270. package/dist/infra/poolbot-root.js +32 -22
  271. package/dist/infra/ports.js +14 -11
  272. package/dist/infra/skills-remote.js +48 -37
  273. package/dist/infra/system-events.js +25 -11
  274. package/dist/infra/system-presence.js +26 -33
  275. package/dist/infra/tmp-poolbot-dir.js +81 -2
  276. package/dist/infra/wsl.js +37 -1
  277. package/dist/line/bot-message-context.js +163 -191
  278. package/dist/logging/subsystem.js +59 -22
  279. package/dist/markdown/ir.js +124 -50
  280. package/dist/media/store.js +1 -1
  281. package/dist/media-understanding/runner.entries.js +42 -25
  282. package/dist/media-understanding/runner.js +53 -488
  283. package/dist/memory/embeddings-gemini.js +53 -38
  284. package/dist/memory/manager-embedding-ops.js +48 -69
  285. package/dist/pairing/pairing-store.js +178 -119
  286. package/dist/plugin-sdk/index.js +34 -6
  287. package/dist/plugins/hooks.js +135 -14
  288. package/dist/plugins/install.js +190 -152
  289. package/dist/polls.js +11 -0
  290. package/dist/routing/resolve-route.js +190 -56
  291. package/dist/routing/session-key.js +38 -22
  292. package/dist/runtime.js +35 -9
  293. package/dist/security/audit-channel.js +1 -1
  294. package/dist/sessions/session-key-utils.js +29 -11
  295. package/dist/shared/frontmatter.js +5 -5
  296. package/dist/shared/node-list-types.js +1 -0
  297. package/dist/shared/string-normalization.js +15 -0
  298. package/dist/signal/monitor/event-handler.js +68 -36
  299. package/dist/signal/send.js +29 -37
  300. package/dist/slack/monitor/allow-list.js +10 -11
  301. package/dist/slack/monitor/commands.js +14 -3
  302. package/dist/slack/monitor/events/interactions.js +4 -4
  303. package/dist/slack/monitor/media.js +224 -16
  304. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  305. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  306. package/dist/slack/monitor/slash.js +357 -144
  307. package/dist/slack/streaming.js +77 -0
  308. package/dist/telegram/accounts.js +40 -13
  309. package/dist/telegram/allowed-updates.js +3 -0
  310. package/dist/telegram/bot/delivery.js +129 -66
  311. package/dist/telegram/bot/helpers.js +136 -122
  312. package/dist/telegram/bot-handlers.js +600 -339
  313. package/dist/telegram/bot-message-context.js +115 -73
  314. package/dist/telegram/bot-message-dispatch.js +235 -104
  315. package/dist/telegram/bot-native-command-menu.js +3 -1
  316. package/dist/telegram/bot-native-commands.js +213 -193
  317. package/dist/telegram/bot.js +24 -132
  318. package/dist/telegram/draft-stream.js +84 -75
  319. package/dist/telegram/format.js +150 -6
  320. package/dist/telegram/send.js +415 -255
  321. package/dist/telegram/targets.js +21 -2
  322. package/dist/telegram/update-offset-store.js +19 -3
  323. package/dist/terminal/restore.js +5 -2
  324. package/dist/test-utils/fetch-mock.js +5 -0
  325. package/dist/version.js +18 -5
  326. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  327. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  328. package/dist/web/inbound/media.js +34 -8
  329. package/dist/web/inbound/monitor.js +34 -17
  330. package/dist/web/inbound/send-api.js +18 -17
  331. package/dist/web/outbound.js +12 -5
  332. package/dist/wizard/clack-prompter.js +40 -7
  333. package/extensions/bluebubbles/package.json +1 -1
  334. package/extensions/copilot-proxy/package.json +1 -1
  335. package/extensions/diagnostics-otel/package.json +1 -1
  336. package/extensions/discord/package.json +1 -1
  337. package/extensions/feishu/package.json +1 -1
  338. package/extensions/google-antigravity-auth/package.json +1 -1
  339. package/extensions/google-gemini-cli-auth/package.json +1 -1
  340. package/extensions/googlechat/package.json +1 -1
  341. package/extensions/imessage/package.json +1 -1
  342. package/extensions/irc/package.json +1 -1
  343. package/extensions/line/package.json +1 -1
  344. package/extensions/llm-task/package.json +1 -1
  345. package/extensions/lobster/package.json +1 -1
  346. package/extensions/matrix/CHANGELOG.md +5 -0
  347. package/extensions/matrix/package.json +1 -1
  348. package/extensions/mattermost/package.json +1 -1
  349. package/extensions/memory-core/package.json +1 -1
  350. package/extensions/memory-lancedb/package.json +1 -1
  351. package/extensions/minimax-portal-auth/package.json +1 -1
  352. package/extensions/msteams/CHANGELOG.md +5 -0
  353. package/extensions/msteams/package.json +1 -1
  354. package/extensions/nextcloud-talk/package.json +1 -1
  355. package/extensions/nostr/CHANGELOG.md +5 -0
  356. package/extensions/nostr/package.json +1 -1
  357. package/extensions/open-prose/package.json +1 -1
  358. package/extensions/openai-codex-auth/package.json +1 -1
  359. package/extensions/signal/package.json +1 -1
  360. package/extensions/slack/package.json +1 -1
  361. package/extensions/telegram/package.json +1 -1
  362. package/extensions/tlon/package.json +1 -1
  363. package/extensions/twitch/CHANGELOG.md +5 -0
  364. package/extensions/twitch/package.json +1 -1
  365. package/extensions/voice-call/CHANGELOG.md +5 -0
  366. package/extensions/voice-call/package.json +1 -1
  367. package/extensions/whatsapp/package.json +1 -1
  368. package/extensions/zalo/CHANGELOG.md +5 -0
  369. package/extensions/zalo/package.json +1 -1
  370. package/extensions/zalouser/CHANGELOG.md +5 -0
  371. package/extensions/zalouser/package.json +1 -1
  372. package/package.json +1 -1
  373. package/skills/apple-reminders/SKILL.md +100 -49
  374. package/skills/coding-agent/SKILL.md +34 -28
  375. package/skills/github/SKILL.md +131 -16
  376. package/skills/imsg/SKILL.md +112 -15
  377. package/skills/openhue/SKILL.md +101 -19
  378. package/skills/plcode-controller/SKILL.md +156 -0
  379. package/skills/plcode-controller/assets/operator-prompts.md +65 -0
  380. package/skills/plcode-controller/references/command-cheatsheet.md +53 -0
  381. package/skills/plcode-controller/references/failure-handling.md +60 -0
  382. package/skills/plcode-controller/references/model-selection.md +57 -0
  383. package/skills/plcode-controller/references/plan-vs-build.md +52 -0
  384. package/skills/plcode-controller/references/question-handling.md +40 -0
  385. package/skills/plcode-controller/references/session-management.md +63 -0
  386. package/skills/plcode-controller/references/workflow.md +35 -0
  387. package/skills/tmux/SKILL.md +111 -79
  388. package/skills/weather/SKILL.md +88 -25
@@ -3,41 +3,53 @@ import os from "node:os";
3
3
  import { streamSimple } from "@mariozechner/pi-ai";
4
4
  import { createAgentSession, SessionManager, SettingsManager } from "@mariozechner/pi-coding-agent";
5
5
  import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js";
6
- import { listChannelSupportedActions, resolveChannelMessageToolHints, } from "../../channel-tools.js";
7
6
  import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
8
7
  import { getMachineDisplayName } from "../../../infra/machine-name.js";
8
+ import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
9
+ import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
10
+ import { isCronSessionKey, isSubagentSessionKey, normalizeAgentId, } from "../../../routing/session-key.js";
11
+ import { resolveSignalReactionLevel } from "../../../signal/reaction-level.js";
9
12
  import { resolveTelegramInlineButtonsScope } from "../../../telegram/inline-buttons.js";
10
13
  import { resolveTelegramReactionLevel } from "../../../telegram/reaction-level.js";
11
- import { resolveSignalReactionLevel } from "../../../signal/reaction-level.js";
14
+ import { buildTtsSystemPromptHint } from "../../../tts/tts.js";
15
+ import { resolveUserPath } from "../../../utils.js";
12
16
  import { normalizeMessageChannel } from "../../../utils/message-channel.js";
13
17
  import { isReasoningTagProvider } from "../../../utils/provider-utils.js";
14
- import { isSubagentSessionKey } from "../../../routing/session-key.js";
15
- import { resolveUserPath } from "../../../utils.js";
16
- import { createCacheTrace } from "../../cache-trace.js";
17
- import { createAnthropicPayloadLogger } from "../../anthropic-payload-log.js";
18
18
  import { resolvePoolbotAgentDir } from "../../agent-paths.js";
19
19
  import { resolveSessionAgentIds } from "../../agent-scope.js";
20
+ import { createAnthropicPayloadLogger } from "../../anthropic-payload-log.js";
20
21
  import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../../bootstrap-files.js";
22
+ import { createCacheTrace } from "../../cache-trace.js";
23
+ import { listChannelSupportedActions, resolveChannelMessageToolHints, } from "../../channel-tools.js";
24
+ import { DEFAULT_CONTEXT_TOKENS } from "../../defaults.js";
21
25
  import { resolvePoolbotDocsPath } from "../../docs-path.js";
26
+ import { isTimeoutError } from "../../failover-error.js";
27
+ import { resolveImageSanitizationLimits } from "../../image-sanitization.js";
22
28
  import { resolveModelAuthMode } from "../../model-auth.js";
23
- import { isCloudCodeAssistFormatError, resolveBootstrapMaxChars, validateAnthropicTurns, validateGeminiTurns, } from "../../pi-embedded-helpers.js";
29
+ import { resolveDefaultModelForAgent } from "../../model-selection.js";
30
+ import { createOllamaStreamFn, OLLAMA_NATIVE_BASE_URL } from "../../ollama-stream.js";
31
+ import { isCloudCodeAssistFormatError, resolveBootstrapMaxChars, resolveBootstrapTotalMaxChars, validateAnthropicTurns, validateGeminiTurns, } from "../../pi-embedded-helpers.js";
24
32
  import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
25
33
  import { ensurePiCompactionReserveTokens, resolveCompactionReserveTokensFloor, } from "../../pi-settings.js";
26
- import { createPoolbotCodingTools } from "../../pi-tools.js";
27
- import { repairSessionFileIfNeeded } from "../../session-file-repair.js";
34
+ import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
35
+ import { createPoolbotCodingTools, resolveToolLoopDetectionConfig } from "../../pi-tools.js";
28
36
  import { resolveSandboxContext } from "../../sandbox.js";
37
+ import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js";
38
+ import { repairSessionFileIfNeeded } from "../../session-file-repair.js";
29
39
  import { guardSessionManager } from "../../session-tool-result-guard-wrapper.js";
30
- import { resolveTranscriptPolicy } from "../../transcript-policy.js";
31
- import { acquireSessionWriteLock } from "../../session-write-lock.js";
40
+ import { sanitizeToolUseResultPairing } from "../../session-transcript-repair.js";
41
+ import { acquireSessionWriteLock, resolveSessionLockMaxHoldFromTimeout, } from "../../session-write-lock.js";
42
+ import { detectRuntimeShell } from "../../shell-utils.js";
32
43
  import { applySkillEnvOverrides, applySkillEnvOverridesFromSnapshot, loadWorkspaceSkillEntries, resolveSkillsPromptForRun, } from "../../skills.js";
33
- import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
44
+ import { buildSystemPromptParams } from "../../system-prompt-params.js";
34
45
  import { buildSystemPromptReport } from "../../system-prompt-report.js";
35
- import { resolveDefaultModelForAgent } from "../../model-selection.js";
36
- import { isAbortError } from "../abort.js";
46
+ import { resolveTranscriptPolicy } from "../../transcript-policy.js";
47
+ import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
48
+ import { isRunnerAbortError } from "../abort.js";
49
+ import { appendCacheTtlTimestamp, isCacheTtlEligibleProvider } from "../cache-ttl.js";
37
50
  import { buildEmbeddedExtensionPaths } from "../extensions.js";
38
51
  import { applyExtraParamsToAgent } from "../extra-params.js";
39
- import { appendCacheTtlTimestamp, isCacheTtlEligibleProvider } from "../cache-ttl.js";
40
- import { logToolSchemasForGoogle, sanitizeSessionHistory, sanitizeToolsForGoogle, } from "../google.js";
52
+ import { logToolSchemasForGoogle, sanitizeAntigravityThinkingBlocks, sanitizeSessionHistory, sanitizeToolsForGoogle, } from "../google.js";
41
53
  import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "../history.js";
42
54
  import { log } from "../logger.js";
43
55
  import { buildModelAliasLines } from "../model.js";
@@ -46,25 +58,22 @@ import { buildEmbeddedSandboxInfo } from "../sandbox-info.js";
46
58
  import { prewarmSessionFile, trackSessionManagerAccess } from "../session-manager-cache.js";
47
59
  import { prepareSessionManagerForRun } from "../session-manager-init.js";
48
60
  import { applySystemPromptOverrideToSession, buildEmbeddedSystemPrompt, createSystemPromptOverride, } from "../system-prompt.js";
61
+ import { installToolResultContextGuard } from "../tool-result-context-guard.js";
49
62
  import { splitSdkTools } from "../tool-split.js";
50
- import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
51
- import { buildSystemPromptParams } from "../../system-prompt-params.js";
52
- import { detectRuntimeShell } from "../../shell-utils.js";
53
63
  import { describeUnknownError, mapThinkingLevel } from "../utils.js";
54
- import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js";
55
- import { buildTtsSystemPromptHint } from "../../../tts/tts.js";
56
- import { isTimeoutError } from "../../failover-error.js";
57
- import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
58
- import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
64
+ import { flushPendingToolResultsAfterIdle } from "../wait-for-idle-before-flush.js";
65
+ import { selectCompactionTimeoutSnapshot, shouldFlagCompactionTimeout, } from "./compaction-timeout.js";
59
66
  import { detectAndLoadPromptImages } from "./images.js";
60
67
  export function injectHistoryImagesIntoMessages(messages, historyImagesByIndex) {
61
- if (historyImagesByIndex.size === 0)
68
+ if (historyImagesByIndex.size === 0) {
62
69
  return false;
70
+ }
63
71
  let didMutate = false;
64
72
  for (const [msgIndex, images] of historyImagesByIndex) {
65
73
  // Bounds check: ensure index is valid before accessing
66
- if (msgIndex < 0 || msgIndex >= messages.length)
74
+ if (msgIndex < 0 || msgIndex >= messages.length) {
67
75
  continue;
76
+ }
68
77
  const msg = messages[msgIndex];
69
78
  if (msg && msg.role === "user") {
70
79
  // Convert string content to array format if needed
@@ -92,6 +101,56 @@ export function injectHistoryImagesIntoMessages(messages, historyImagesByIndex)
92
101
  }
93
102
  return didMutate;
94
103
  }
104
+ function summarizeMessagePayload(msg) {
105
+ const content = msg.content;
106
+ if (typeof content === "string") {
107
+ return { textChars: content.length, imageBlocks: 0 };
108
+ }
109
+ if (!Array.isArray(content)) {
110
+ return { textChars: 0, imageBlocks: 0 };
111
+ }
112
+ let textChars = 0;
113
+ let imageBlocks = 0;
114
+ for (const block of content) {
115
+ if (!block || typeof block !== "object") {
116
+ continue;
117
+ }
118
+ const typedBlock = block;
119
+ if (typedBlock.type === "image") {
120
+ imageBlocks++;
121
+ continue;
122
+ }
123
+ if (typeof typedBlock.text === "string") {
124
+ textChars += typedBlock.text.length;
125
+ }
126
+ }
127
+ return { textChars, imageBlocks };
128
+ }
129
+ function summarizeSessionContext(messages) {
130
+ const roleCounts = new Map();
131
+ let totalTextChars = 0;
132
+ let totalImageBlocks = 0;
133
+ let maxMessageTextChars = 0;
134
+ for (const msg of messages) {
135
+ const role = typeof msg.role === "string" ? msg.role : "unknown";
136
+ roleCounts.set(role, (roleCounts.get(role) ?? 0) + 1);
137
+ const payload = summarizeMessagePayload(msg);
138
+ totalTextChars += payload.textChars;
139
+ totalImageBlocks += payload.imageBlocks;
140
+ if (payload.textChars > maxMessageTextChars) {
141
+ maxMessageTextChars = payload.textChars;
142
+ }
143
+ }
144
+ return {
145
+ roleCounts: [...roleCounts.entries()]
146
+ .toSorted((a, b) => a[0].localeCompare(b[0]))
147
+ .map(([role, count]) => `${role}:${count}`)
148
+ .join(",") || "none",
149
+ totalTextChars,
150
+ totalImageBlocks,
151
+ maxMessageTextChars,
152
+ };
153
+ }
95
154
  export async function runEmbeddedAttempt(params) {
96
155
  const resolvedWorkspace = resolveUserPath(params.workspaceDir);
97
156
  const prevCwd = process.cwd();
@@ -174,6 +233,7 @@ export async function runEmbeddedAttempt(params) {
174
233
  abortSignal: runAbortController.signal,
175
234
  modelProvider: params.model.provider,
176
235
  modelId: params.modelId,
236
+ modelContextWindowTokens: params.model.contextWindow,
177
237
  modelAuthMode: resolveModelAuthMode(params.model.provider, params.config),
178
238
  currentChannelId: params.currentChannelId,
179
239
  currentThreadTs: params.currentThreadTs,
@@ -200,8 +260,9 @@ export async function runEmbeddedAttempt(params) {
200
260
  accountId: params.agentAccountId ?? undefined,
201
261
  });
202
262
  if (inlineButtonsScope !== "off") {
203
- if (!runtimeCapabilities)
263
+ if (!runtimeCapabilities) {
204
264
  runtimeCapabilities = [];
265
+ }
205
266
  if (!runtimeCapabilities.some((cap) => String(cap).trim().toLowerCase() === "inlinebuttons")) {
206
267
  runtimeCapabilities.push("inlineButtons");
207
268
  }
@@ -265,14 +326,16 @@ export async function runEmbeddedAttempt(params) {
265
326
  node: process.version,
266
327
  model: `${params.provider}/${params.modelId}`,
267
328
  defaultModel: defaultModelLabel,
329
+ shell: detectRuntimeShell(),
268
330
  channel: runtimeChannel,
269
331
  capabilities: runtimeCapabilities,
270
332
  channelActions,
271
- shell: detectRuntimeShell(),
272
333
  },
273
334
  });
274
335
  const isDefaultAgent = sessionAgentId === defaultAgentId;
275
- const promptMode = isSubagentSessionKey(params.sessionKey) ? "minimal" : "full";
336
+ const promptMode = isSubagentSessionKey(params.sessionKey) || isCronSessionKey(params.sessionKey)
337
+ ? "minimal"
338
+ : "full";
276
339
  const docsPath = await resolvePoolbotDocsPath({
277
340
  workspaceDir: effectiveWorkspace,
278
341
  argv1: process.argv[1],
@@ -316,6 +379,7 @@ export async function runEmbeddedAttempt(params) {
316
379
  model: params.modelId,
317
380
  workspaceDir: effectiveWorkspace,
318
381
  bootstrapMaxChars: resolveBootstrapMaxChars(params.config),
382
+ bootstrapTotalMaxChars: resolveBootstrapTotalMaxChars(params.config),
319
383
  sandbox: (() => {
320
384
  const runtime = resolveSandboxRuntimeStatus({
321
385
  cfg: params.config,
@@ -333,9 +397,13 @@ export async function runEmbeddedAttempt(params) {
333
397
  const systemPromptText = systemPromptOverride();
334
398
  const sessionLock = await acquireSessionWriteLock({
335
399
  sessionFile: params.sessionFile,
400
+ maxHoldMs: resolveSessionLockMaxHoldFromTimeout({
401
+ timeoutMs: params.timeoutMs,
402
+ }),
336
403
  });
337
404
  let sessionManager;
338
405
  let session;
406
+ let removeToolResultContextGuard;
339
407
  try {
340
408
  await repairSessionFileIfNeeded({
341
409
  sessionFile: params.sessionFile,
@@ -354,6 +422,7 @@ export async function runEmbeddedAttempt(params) {
354
422
  sessionManager = guardSessionManager(SessionManager.open(params.sessionFile), {
355
423
  agentId: sessionAgentId,
356
424
  sessionKey: params.sessionKey,
425
+ inputProvenance: params.inputProvenance,
357
426
  allowSyntheticToolResults: transcriptPolicy.allowSyntheticToolResults,
358
427
  });
359
428
  trackSessionManagerAccess(params.sessionFile);
@@ -369,6 +438,7 @@ export async function runEmbeddedAttempt(params) {
369
438
  settingsManager,
370
439
  minReserveTokens: resolveCompactionReserveTokensFloor(params.config),
371
440
  });
441
+ // Call for side effects (sets compaction/pruning runtime state)
372
442
  buildEmbeddedExtensionPaths({
373
443
  cfg: params.config,
374
444
  sessionManager,
@@ -376,16 +446,26 @@ export async function runEmbeddedAttempt(params) {
376
446
  modelId: params.modelId,
377
447
  model: params.model,
378
448
  });
449
+ // Get hook runner early so it's available when creating tools
450
+ const hookRunner = getGlobalHookRunner();
379
451
  const { builtInTools, customTools } = splitSdkTools({
380
452
  tools,
381
453
  sandboxEnabled: !!sandbox?.enabled,
382
454
  });
383
455
  // Add client tools (OpenResponses hosted tools) to customTools
384
456
  let clientToolCallDetected = null;
457
+ const clientToolLoopDetection = resolveToolLoopDetectionConfig({
458
+ cfg: params.config,
459
+ agentId: sessionAgentId,
460
+ });
385
461
  const clientToolDefs = params.clientTools
386
462
  ? toClientToolDefinitions(params.clientTools, (toolName, toolParams) => {
387
463
  clientToolCallDetected = { name: toolName, params: toolParams };
388
- }, { agentId: sessionAgentId, sessionKey: params.sessionKey })
464
+ }, {
465
+ agentId: sessionAgentId,
466
+ sessionKey: params.sessionKey,
467
+ loopDetection: clientToolLoopDetection,
468
+ })
389
469
  : [];
390
470
  const allCustomTools = [...customTools, ...clientToolDefs];
391
471
  ({ session } = await createAgentSession({
@@ -405,6 +485,10 @@ export async function runEmbeddedAttempt(params) {
405
485
  throw new Error("Embedded agent session missing");
406
486
  }
407
487
  const activeSession = session;
488
+ removeToolResultContextGuard = installToolResultContextGuard({
489
+ agent: activeSession.agent,
490
+ contextWindowTokens: Math.max(1, Math.floor(params.model.contextWindow ?? params.model.maxTokens ?? DEFAULT_CONTEXT_TOKENS)),
491
+ });
408
492
  const cacheTrace = createCacheTrace({
409
493
  cfg: params.config,
410
494
  env: process.env,
@@ -426,8 +510,20 @@ export async function runEmbeddedAttempt(params) {
426
510
  modelApi: params.model.api,
427
511
  workspaceDir: params.workspaceDir,
428
512
  });
429
- // Force a stable streamFn reference so vitest can reliably mock @mariozechner/pi-ai.
430
- activeSession.agent.streamFn = streamSimple;
513
+ // Ollama native API: bypass SDK's streamSimple and use direct /api/chat calls
514
+ // for reliable streaming + tool calling support (#11828).
515
+ if (params.model.api === "ollama") {
516
+ // Use the resolved model baseUrl first so custom provider aliases work.
517
+ const providerConfig = params.config?.models?.providers?.[params.model.provider];
518
+ const modelBaseUrl = typeof params.model.baseUrl === "string" ? params.model.baseUrl.trim() : "";
519
+ const providerBaseUrl = typeof providerConfig?.baseUrl === "string" ? providerConfig.baseUrl.trim() : "";
520
+ const ollamaBaseUrl = modelBaseUrl || providerBaseUrl || OLLAMA_NATIVE_BASE_URL;
521
+ activeSession.agent.streamFn = createOllamaStreamFn(ollamaBaseUrl);
522
+ }
523
+ else {
524
+ // Force a stable streamFn reference so vitest can reliably mock @mariozechner/pi-ai.
525
+ activeSession.agent.streamFn = streamSimple;
526
+ }
431
527
  applyExtraParamsToAgent(activeSession.agent, params.config, params.provider, params.modelId, params.streamParams);
432
528
  if (cacheTrace) {
433
529
  cacheTrace.recordStage("session:loaded", {
@@ -446,6 +542,7 @@ export async function runEmbeddedAttempt(params) {
446
542
  modelApi: params.model.api,
447
543
  modelId: params.modelId,
448
544
  provider: params.provider,
545
+ config: params.config,
449
546
  sessionManager,
450
547
  sessionId: params.sessionId,
451
548
  policy: transcriptPolicy,
@@ -457,19 +554,29 @@ export async function runEmbeddedAttempt(params) {
457
554
  const validated = transcriptPolicy.validateAnthropicTurns
458
555
  ? validateAnthropicTurns(validatedGemini)
459
556
  : validatedGemini;
460
- const limited = limitHistoryTurns(validated, getDmHistoryLimitFromSessionKey(params.sessionKey, params.config));
557
+ const truncated = limitHistoryTurns(validated, getDmHistoryLimitFromSessionKey(params.sessionKey, params.config));
558
+ // Re-run tool_use/tool_result pairing repair after truncation, since
559
+ // limitHistoryTurns can orphan tool_result blocks by removing the
560
+ // assistant message that contained the matching tool_use.
561
+ const limited = transcriptPolicy.repairToolUseResultPairing
562
+ ? sanitizeToolUseResultPairing(truncated)
563
+ : truncated;
461
564
  cacheTrace?.recordStage("session:limited", { messages: limited });
462
565
  if (limited.length > 0) {
463
566
  activeSession.agent.replaceMessages(limited);
464
567
  }
465
568
  }
466
569
  catch (err) {
467
- sessionManager.flushPendingToolResults?.();
570
+ await flushPendingToolResultsAfterIdle({
571
+ agent: activeSession?.agent,
572
+ sessionManager,
573
+ });
468
574
  activeSession.dispose();
469
575
  throw err;
470
576
  }
471
577
  let aborted = Boolean(params.abortSignal?.aborted);
472
578
  let timedOut = false;
579
+ let timedOutDuringCompaction = false;
473
580
  const getAbortReason = (signal) => "reason" in signal ? signal.reason : undefined;
474
581
  const makeTimeoutAbortReason = () => {
475
582
  const err = new Error("request timed out");
@@ -484,8 +591,9 @@ export async function runEmbeddedAttempt(params) {
484
591
  };
485
592
  const abortRun = (isTimeout = false, reason) => {
486
593
  aborted = true;
487
- if (isTimeout)
594
+ if (isTimeout) {
488
595
  timedOut = true;
596
+ }
489
597
  if (isTimeout) {
490
598
  runAbortController.abort(reason ?? makeTimeoutAbortReason());
491
599
  }
@@ -517,6 +625,7 @@ export async function runEmbeddedAttempt(params) {
517
625
  const subscription = subscribeEmbeddedPiSession({
518
626
  session: activeSession,
519
627
  runId: params.runId,
628
+ hookRunner: getGlobalHookRunner() ?? undefined,
520
629
  verboseLevel: params.verboseLevel,
521
630
  reasoningMode: params.reasoningLevel ?? "off",
522
631
  toolResultFormat: params.toolResultFormat,
@@ -524,6 +633,7 @@ export async function runEmbeddedAttempt(params) {
524
633
  shouldEmitToolOutput: params.shouldEmitToolOutput,
525
634
  onToolResult: params.onToolResult,
526
635
  onReasoningStream: params.onReasoningStream,
636
+ onReasoningEnd: params.onReasoningEnd,
527
637
  onBlockReply: params.onBlockReply,
528
638
  onBlockReplyFlush: params.onBlockReplyFlush,
529
639
  blockReplyBreak: params.blockReplyBreak,
@@ -532,8 +642,10 @@ export async function runEmbeddedAttempt(params) {
532
642
  onAssistantMessageStart: params.onAssistantMessageStart,
533
643
  onAgentEvent: params.onAgentEvent,
534
644
  enforceFinalTag: params.enforceFinalTag,
645
+ config: params.config,
646
+ sessionKey: params.sessionKey ?? params.sessionId,
535
647
  });
536
- const { assistantTexts, toolMetas, unsubscribe, waitForCompactionRetry, getMessagingToolSentTexts, getMessagingToolSentTargets, didSendViaMessagingTool, getLastToolError, } = subscription;
648
+ const { assistantTexts, toolMetas, unsubscribe, waitForCompactionRetry, getMessagingToolSentTexts, getMessagingToolSentMediaUrls, getMessagingToolSentTargets, getSuccessfulCronAdds, didSendViaMessagingTool, getLastToolError, getUsageTotals, getCompactionCount, } = subscription;
537
649
  const queueHandle = {
538
650
  queueMessage: async (text) => {
539
651
  await activeSession.steer(text);
@@ -542,18 +654,26 @@ export async function runEmbeddedAttempt(params) {
542
654
  isCompacting: () => subscription.isCompacting(),
543
655
  abort: abortRun,
544
656
  };
545
- setActiveEmbeddedRun(params.sessionId, queueHandle);
657
+ setActiveEmbeddedRun(params.sessionId, queueHandle, params.sessionKey);
546
658
  let abortWarnTimer;
547
659
  const isProbeSession = params.sessionId?.startsWith("probe-") ?? false;
548
660
  const abortTimer = setTimeout(() => {
549
661
  if (!isProbeSession) {
550
662
  log.warn(`embedded run timeout: runId=${params.runId} sessionId=${params.sessionId} timeoutMs=${params.timeoutMs}`);
551
663
  }
664
+ if (shouldFlagCompactionTimeout({
665
+ isTimeout: true,
666
+ isCompactionPendingOrRetrying: subscription.isCompacting(),
667
+ isCompactionInFlight: activeSession.isCompacting,
668
+ })) {
669
+ timedOutDuringCompaction = true;
670
+ }
552
671
  abortRun(true);
553
672
  if (!abortWarnTimer) {
554
673
  abortWarnTimer = setTimeout(() => {
555
- if (!activeSession.isStreaming)
674
+ if (!activeSession.isStreaming) {
556
675
  return;
676
+ }
557
677
  if (!isProbeSession) {
558
678
  log.warn(`embedded run abort still streaming: runId=${params.runId} sessionId=${params.sessionId}`);
559
679
  }
@@ -565,6 +685,13 @@ export async function runEmbeddedAttempt(params) {
565
685
  const onAbort = () => {
566
686
  const reason = params.abortSignal ? getAbortReason(params.abortSignal) : undefined;
567
687
  const timeout = reason ? isTimeoutError(reason) : false;
688
+ if (shouldFlagCompactionTimeout({
689
+ isTimeout: timeout,
690
+ isCompactionPendingOrRetrying: subscription.isCompacting(),
691
+ isCompactionInFlight: activeSession.isCompacting,
692
+ })) {
693
+ timedOutDuringCompaction = true;
694
+ }
568
695
  abortRun(timeout, reason);
569
696
  };
570
697
  if (params.abortSignal) {
@@ -577,32 +704,59 @@ export async function runEmbeddedAttempt(params) {
577
704
  });
578
705
  }
579
706
  }
580
- // Get hook runner once for both before_agent_start and agent_end hooks
581
- const hookRunner = getGlobalHookRunner();
582
- const hookAgentId = sessionAgentId;
707
+ // Hook runner was already obtained earlier before tool creation
708
+ const hookAgentId = typeof params.agentId === "string" && params.agentId.trim()
709
+ ? normalizeAgentId(params.agentId)
710
+ : resolveSessionAgentIds({
711
+ sessionKey: params.sessionKey,
712
+ config: params.config,
713
+ }).sessionAgentId;
583
714
  let promptError = null;
715
+ let promptErrorSource = null;
584
716
  try {
585
717
  const promptStartedAt = Date.now();
586
- // Run before_agent_start hooks to allow plugins to inject context
718
+ // Run before_prompt_build hooks to allow plugins to inject prompt context.
719
+ // Legacy compatibility: before_agent_start is also checked for context fields.
587
720
  let effectivePrompt = params.prompt;
588
- if (hookRunner?.hasHooks("before_agent_start")) {
589
- try {
590
- const hookResult = await hookRunner.runBeforeAgentStart({
591
- prompt: params.prompt,
592
- messages: activeSession.messages,
593
- }, {
594
- agentId: hookAgentId,
595
- sessionKey: params.sessionKey,
596
- workspaceDir: params.workspaceDir,
597
- messageProvider: params.messageProvider ?? undefined,
598
- });
599
- if (hookResult?.prependContext) {
600
- effectivePrompt = `${hookResult.prependContext}\n\n${params.prompt}`;
601
- log.debug(`hooks: prepended context to prompt (${hookResult.prependContext.length} chars)`);
602
- }
603
- }
604
- catch (hookErr) {
605
- log.warn(`before_agent_start hook failed: ${String(hookErr)}`);
721
+ const hookCtx = {
722
+ agentId: hookAgentId,
723
+ sessionKey: params.sessionKey,
724
+ sessionId: params.sessionId,
725
+ workspaceDir: params.workspaceDir,
726
+ messageProvider: params.messageProvider ?? undefined,
727
+ };
728
+ const promptBuildResult = hookRunner?.hasHooks("before_prompt_build")
729
+ ? await hookRunner
730
+ .runBeforePromptBuild({
731
+ prompt: params.prompt,
732
+ messages: activeSession.messages,
733
+ }, hookCtx)
734
+ .catch((hookErr) => {
735
+ log.warn(`before_prompt_build hook failed: ${String(hookErr)}`);
736
+ return undefined;
737
+ })
738
+ : undefined;
739
+ const legacyResult = hookRunner?.hasHooks("before_agent_start")
740
+ ? await hookRunner
741
+ .runBeforeAgentStart({
742
+ prompt: params.prompt,
743
+ messages: activeSession.messages,
744
+ }, hookCtx)
745
+ .catch((hookErr) => {
746
+ log.warn(`before_agent_start hook (legacy prompt build path) failed: ${String(hookErr)}`);
747
+ return undefined;
748
+ })
749
+ : undefined;
750
+ const hookResult = {
751
+ systemPrompt: promptBuildResult?.systemPrompt ?? legacyResult?.systemPrompt,
752
+ prependContext: [promptBuildResult?.prependContext, legacyResult?.prependContext]
753
+ .filter((value) => Boolean(value))
754
+ .join("\n\n"),
755
+ };
756
+ {
757
+ if (hookResult?.prependContext) {
758
+ effectivePrompt = `${hookResult.prependContext}\n\n${params.prompt}`;
759
+ log.debug(`hooks: prepended context to prompt (${hookResult.prependContext.length} chars)`);
606
760
  }
607
761
  }
608
762
  log.debug(`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId}`);
@@ -620,7 +774,10 @@ export async function runEmbeddedAttempt(params) {
620
774
  sessionManager.resetLeaf();
621
775
  }
622
776
  const sessionContext = sessionManager.buildSessionContext();
623
- activeSession.agent.replaceMessages(sessionContext.messages);
777
+ const sanitizedOrphan = transcriptPolicy.normalizeAntigravityThinkingBlocks
778
+ ? sanitizeAntigravityThinkingBlocks(sessionContext.messages)
779
+ : sessionContext.messages;
780
+ activeSession.agent.replaceMessages(sanitizedOrphan);
624
781
  log.warn(`Removed orphaned user message to prevent consecutive user turns. ` +
625
782
  `runId=${params.runId} sessionId=${params.sessionId}`);
626
783
  }
@@ -636,8 +793,11 @@ export async function runEmbeddedAttempt(params) {
636
793
  existingImages: params.images,
637
794
  historyMessages: activeSession.messages,
638
795
  maxBytes: MAX_IMAGE_BYTES,
796
+ maxDimensionPx: resolveImageSanitizationLimits(params.config).maxDimensionPx,
639
797
  // Enforce sandbox path restrictions when sandbox is enabled
640
- sandboxRoot: sandbox?.enabled ? sandbox.workspaceDir : undefined,
798
+ sandbox: sandbox?.enabled && sandbox?.fsBridge
799
+ ? { root: sandbox.workspaceDir, bridge: sandbox.fsBridge }
800
+ : undefined,
641
801
  });
642
802
  // Inject history images into their original message positions.
643
803
  // This ensures the model sees images in context (e.g., "compare to the first image").
@@ -651,13 +811,42 @@ export async function runEmbeddedAttempt(params) {
651
811
  messages: activeSession.messages,
652
812
  note: `images: prompt=${imageResult.images.length} history=${imageResult.historyImagesByIndex.size}`,
653
813
  });
654
- const shouldTrackCacheTtl = params.config?.agents?.defaults?.contextPruning?.mode === "cache-ttl" &&
655
- isCacheTtlEligibleProvider(params.provider, params.modelId);
656
- if (shouldTrackCacheTtl) {
657
- appendCacheTtlTimestamp(sessionManager, {
658
- timestamp: Date.now(),
814
+ // Diagnostic: log context sizes before prompt to help debug early overflow errors.
815
+ if (log.isEnabled("debug")) {
816
+ const msgCount = activeSession.messages.length;
817
+ const systemLen = systemPromptText?.length ?? 0;
818
+ const promptLen = effectivePrompt.length;
819
+ const sessionSummary = summarizeSessionContext(activeSession.messages);
820
+ log.debug(`[context-diag] pre-prompt: sessionKey=${params.sessionKey ?? params.sessionId} ` +
821
+ `messages=${msgCount} roleCounts=${sessionSummary.roleCounts} ` +
822
+ `historyTextChars=${sessionSummary.totalTextChars} ` +
823
+ `maxMessageTextChars=${sessionSummary.maxMessageTextChars} ` +
824
+ `historyImageBlocks=${sessionSummary.totalImageBlocks} ` +
825
+ `systemPromptChars=${systemLen} promptChars=${promptLen} ` +
826
+ `promptImages=${imageResult.images.length} ` +
827
+ `historyImageMessages=${imageResult.historyImagesByIndex.size} ` +
828
+ `provider=${params.provider}/${params.modelId} sessionFile=${params.sessionFile}`);
829
+ }
830
+ if (hookRunner?.hasHooks("llm_input")) {
831
+ hookRunner
832
+ .runLlmInput({
833
+ runId: params.runId,
834
+ sessionId: params.sessionId,
659
835
  provider: params.provider,
660
- modelId: params.modelId,
836
+ model: params.modelId,
837
+ systemPrompt: systemPromptText,
838
+ prompt: effectivePrompt,
839
+ historyMessages: activeSession.messages,
840
+ imagesCount: imageResult.images.length,
841
+ }, {
842
+ agentId: hookAgentId,
843
+ sessionKey: params.sessionKey,
844
+ sessionId: params.sessionId,
845
+ workspaceDir: params.workspaceDir,
846
+ messageProvider: params.messageProvider ?? undefined,
847
+ })
848
+ .catch((err) => {
849
+ log.warn(`llm_input hook failed: ${String(err)}`);
661
850
  });
662
851
  }
663
852
  // Only pass images option if there are actually images to pass
@@ -671,31 +860,98 @@ export async function runEmbeddedAttempt(params) {
671
860
  }
672
861
  catch (err) {
673
862
  promptError = err;
863
+ promptErrorSource = "prompt";
674
864
  }
675
865
  finally {
676
866
  log.debug(`embedded run prompt end: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - promptStartedAt}`);
677
867
  }
868
+ // Capture snapshot before compaction wait so we have complete messages if timeout occurs
869
+ // Check compaction state before and after to avoid race condition where compaction starts during capture
870
+ // Use session state (not subscription) for snapshot decisions - need instantaneous compaction status
871
+ const wasCompactingBefore = activeSession.isCompacting;
872
+ const snapshot = activeSession.messages.slice();
873
+ const wasCompactingAfter = activeSession.isCompacting;
874
+ // Only trust snapshot if compaction wasn't running before or after capture
875
+ const preCompactionSnapshot = wasCompactingBefore || wasCompactingAfter ? null : snapshot;
876
+ const preCompactionSessionId = activeSession.sessionId;
678
877
  try {
679
- await waitForCompactionRetry();
878
+ await abortable(waitForCompactionRetry());
680
879
  }
681
880
  catch (err) {
682
- if (isAbortError(err)) {
683
- if (!promptError)
881
+ if (isRunnerAbortError(err)) {
882
+ if (!promptError) {
684
883
  promptError = err;
884
+ promptErrorSource = "compaction";
885
+ }
886
+ if (!isProbeSession) {
887
+ log.debug(`compaction wait aborted: runId=${params.runId} sessionId=${params.sessionId}`);
888
+ }
685
889
  }
686
890
  else {
687
891
  throw err;
688
892
  }
689
893
  }
690
- messagesSnapshot = activeSession.messages.slice();
691
- sessionIdUsed = activeSession.sessionId;
894
+ // Append cache-TTL timestamp AFTER prompt + compaction retry completes.
895
+ // Previously this was before the prompt, which caused a custom entry to be
896
+ // inserted between compaction and the next prompt — breaking the
897
+ // prepareCompaction() guard that checks the last entry type, leading to
898
+ // double-compaction. See: https://github.com/poolbot/poolbot/issues/9282
899
+ // Skip when timed out during compaction — session state may be inconsistent.
900
+ if (!timedOutDuringCompaction) {
901
+ const shouldTrackCacheTtl = params.config?.agents?.defaults?.contextPruning?.mode === "cache-ttl" &&
902
+ isCacheTtlEligibleProvider(params.provider, params.modelId);
903
+ if (shouldTrackCacheTtl) {
904
+ appendCacheTtlTimestamp(sessionManager, {
905
+ timestamp: Date.now(),
906
+ provider: params.provider,
907
+ modelId: params.modelId,
908
+ });
909
+ }
910
+ }
911
+ // If timeout occurred during compaction, use pre-compaction snapshot when available
912
+ // (compaction restructures messages but does not add user/assistant turns).
913
+ const snapshotSelection = selectCompactionTimeoutSnapshot({
914
+ timedOutDuringCompaction,
915
+ preCompactionSnapshot,
916
+ preCompactionSessionId,
917
+ currentSnapshot: activeSession.messages.slice(),
918
+ currentSessionId: activeSession.sessionId,
919
+ });
920
+ if (timedOutDuringCompaction) {
921
+ if (!isProbeSession) {
922
+ log.warn(`using ${snapshotSelection.source} snapshot: timed out during compaction runId=${params.runId} sessionId=${params.sessionId}`);
923
+ }
924
+ }
925
+ messagesSnapshot = snapshotSelection.messagesSnapshot;
926
+ sessionIdUsed = snapshotSelection.sessionIdUsed;
927
+ if (promptError && promptErrorSource === "prompt") {
928
+ try {
929
+ sessionManager.appendCustomEntry("poolbot:prompt-error", {
930
+ timestamp: Date.now(),
931
+ runId: params.runId,
932
+ sessionId: params.sessionId,
933
+ provider: params.provider,
934
+ model: params.modelId,
935
+ api: params.model.api,
936
+ error: describeUnknownError(promptError),
937
+ });
938
+ }
939
+ catch (entryErr) {
940
+ log.warn(`failed to persist prompt error entry: ${String(entryErr)}`);
941
+ }
942
+ }
692
943
  cacheTrace?.recordStage("session:after", {
693
944
  messages: messagesSnapshot,
694
- note: promptError ? "prompt error" : undefined,
945
+ note: timedOutDuringCompaction
946
+ ? "compaction timeout"
947
+ : promptError
948
+ ? "prompt error"
949
+ : undefined,
695
950
  });
696
951
  anthropicPayloadLogger?.recordUsage(messagesSnapshot, promptError);
697
952
  // Run agent_end hooks to allow plugins to analyze the conversation
698
953
  // This is fire-and-forget, so we don't await
954
+ // Run even on compaction timeout so plugins can log/cleanup
699
955
  if (hookRunner?.hasHooks("agent_end")) {
700
956
  hookRunner
701
957
  .runAgentEnd({
@@ -706,6 +962,7 @@ export async function runEmbeddedAttempt(params) {
706
962
  }, {
707
963
  agentId: hookAgentId,
708
964
  sessionKey: params.sessionKey,
965
+ sessionId: params.sessionId,
709
966
  workspaceDir: params.workspaceDir,
710
967
  messageProvider: params.messageProvider ?? undefined,
711
968
  })
@@ -716,22 +973,56 @@ export async function runEmbeddedAttempt(params) {
716
973
  }
717
974
  finally {
718
975
  clearTimeout(abortTimer);
719
- if (abortWarnTimer)
976
+ if (abortWarnTimer) {
720
977
  clearTimeout(abortWarnTimer);
721
- unsubscribe();
722
- clearActiveEmbeddedRun(params.sessionId, queueHandle);
978
+ }
979
+ if (!isProbeSession && (aborted || timedOut) && !timedOutDuringCompaction) {
980
+ log.debug(`run cleanup: runId=${params.runId} sessionId=${params.sessionId} aborted=${aborted} timedOut=${timedOut}`);
981
+ }
982
+ try {
983
+ unsubscribe();
984
+ }
985
+ catch (err) {
986
+ // unsubscribe() should never throw; if it does, it indicates a serious bug.
987
+ // Log at error level to ensure visibility, but don't rethrow in finally block
988
+ // as it would mask any exception from the try block above.
989
+ log.error(`CRITICAL: unsubscribe failed, possible resource leak: runId=${params.runId} ${String(err)}`);
990
+ }
991
+ clearActiveEmbeddedRun(params.sessionId, queueHandle, params.sessionKey);
723
992
  params.abortSignal?.removeEventListener?.("abort", onAbort);
724
993
  }
725
994
  const lastAssistant = messagesSnapshot
726
995
  .slice()
727
- .reverse()
728
- .find((m) => m?.role === "assistant");
996
+ .toReversed()
997
+ .find((m) => m.role === "assistant");
729
998
  const toolMetasNormalized = toolMetas
730
999
  .filter((entry) => typeof entry.toolName === "string" && entry.toolName.trim().length > 0)
731
1000
  .map((entry) => ({ toolName: entry.toolName, meta: entry.meta }));
1001
+ if (hookRunner?.hasHooks("llm_output")) {
1002
+ hookRunner
1003
+ .runLlmOutput({
1004
+ runId: params.runId,
1005
+ sessionId: params.sessionId,
1006
+ provider: params.provider,
1007
+ model: params.modelId,
1008
+ assistantTexts,
1009
+ lastAssistant,
1010
+ usage: getUsageTotals(),
1011
+ }, {
1012
+ agentId: hookAgentId,
1013
+ sessionKey: params.sessionKey,
1014
+ sessionId: params.sessionId,
1015
+ workspaceDir: params.workspaceDir,
1016
+ messageProvider: params.messageProvider ?? undefined,
1017
+ })
1018
+ .catch((err) => {
1019
+ log.warn(`llm_output hook failed: ${String(err)}`);
1020
+ });
1021
+ }
732
1022
  return {
733
1023
  aborted,
734
1024
  timedOut,
1025
+ timedOutDuringCompaction,
735
1026
  promptError,
736
1027
  sessionIdUsed,
737
1028
  systemPromptReport,
@@ -742,15 +1033,30 @@ export async function runEmbeddedAttempt(params) {
742
1033
  lastToolError: getLastToolError?.(),
743
1034
  didSendViaMessagingTool: didSendViaMessagingTool(),
744
1035
  messagingToolSentTexts: getMessagingToolSentTexts(),
1036
+ messagingToolSentMediaUrls: getMessagingToolSentMediaUrls(),
745
1037
  messagingToolSentTargets: getMessagingToolSentTargets(),
1038
+ successfulCronAdds: getSuccessfulCronAdds(),
746
1039
  cloudCodeAssistFormatError: Boolean(lastAssistant?.errorMessage && isCloudCodeAssistFormatError(lastAssistant.errorMessage)),
1040
+ attemptUsage: getUsageTotals(),
1041
+ compactionCount: getCompactionCount(),
747
1042
  // Client tool call detected (OpenResponses hosted tools)
748
1043
  clientToolCall: clientToolCallDetected ?? undefined,
749
1044
  };
750
1045
  }
751
1046
  finally {
752
1047
  // Always tear down the session (and release the lock) before we leave this attempt.
753
- sessionManager?.flushPendingToolResults?.();
1048
+ //
1049
+ // BUGFIX: Wait for the agent to be truly idle before flushing pending tool results.
1050
+ // pi-agent-core's auto-retry resolves waitForRetry() on assistant message receipt,
1051
+ // *before* tool execution completes in the retried agent loop. Without this wait,
1052
+ // flushPendingToolResults() fires while tools are still executing, inserting
1053
+ // synthetic "missing tool result" errors and causing silent agent failures.
1054
+ // See: https://github.com/poolbot/poolbot/issues/8643
1055
+ removeToolResultContextGuard?.();
1056
+ await flushPendingToolResultsAfterIdle({
1057
+ agent: session?.agent,
1058
+ sessionManager,
1059
+ });
754
1060
  session?.dispose();
755
1061
  await sessionLock.release();
756
1062
  }