@poolzin/pool-bot 2026.2.21 → 2026.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -3,42 +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 { recordRequestOutcome } from "../../provider/integration.js";
24
- 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";
25
32
  import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
26
33
  import { ensurePiCompactionReserveTokens, resolveCompactionReserveTokensFloor, } from "../../pi-settings.js";
27
- import { createPoolbotCodingTools } from "../../pi-tools.js";
28
- import { repairSessionFileIfNeeded } from "../../session-file-repair.js";
34
+ import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
35
+ import { createPoolbotCodingTools, resolveToolLoopDetectionConfig } from "../../pi-tools.js";
29
36
  import { resolveSandboxContext } from "../../sandbox.js";
37
+ import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js";
38
+ import { repairSessionFileIfNeeded } from "../../session-file-repair.js";
30
39
  import { guardSessionManager } from "../../session-tool-result-guard-wrapper.js";
31
- import { resolveTranscriptPolicy } from "../../transcript-policy.js";
32
- 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";
33
43
  import { applySkillEnvOverrides, applySkillEnvOverridesFromSnapshot, loadWorkspaceSkillEntries, resolveSkillsPromptForRun, } from "../../skills.js";
34
- import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
44
+ import { buildSystemPromptParams } from "../../system-prompt-params.js";
35
45
  import { buildSystemPromptReport } from "../../system-prompt-report.js";
36
- import { resolveDefaultModelForAgent } from "../../model-selection.js";
37
- 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";
38
50
  import { buildEmbeddedExtensionPaths } from "../extensions.js";
39
51
  import { applyExtraParamsToAgent } from "../extra-params.js";
40
- import { appendCacheTtlTimestamp, isCacheTtlEligibleProvider } from "../cache-ttl.js";
41
- import { logToolSchemasForGoogle, sanitizeSessionHistory, sanitizeToolsForGoogle, } from "../google.js";
52
+ import { logToolSchemasForGoogle, sanitizeAntigravityThinkingBlocks, sanitizeSessionHistory, sanitizeToolsForGoogle, } from "../google.js";
42
53
  import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "../history.js";
43
54
  import { log } from "../logger.js";
44
55
  import { buildModelAliasLines } from "../model.js";
@@ -47,25 +58,22 @@ import { buildEmbeddedSandboxInfo } from "../sandbox-info.js";
47
58
  import { prewarmSessionFile, trackSessionManagerAccess } from "../session-manager-cache.js";
48
59
  import { prepareSessionManagerForRun } from "../session-manager-init.js";
49
60
  import { applySystemPromptOverrideToSession, buildEmbeddedSystemPrompt, createSystemPromptOverride, } from "../system-prompt.js";
61
+ import { installToolResultContextGuard } from "../tool-result-context-guard.js";
50
62
  import { splitSdkTools } from "../tool-split.js";
51
- import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
52
- import { buildSystemPromptParams } from "../../system-prompt-params.js";
53
- import { detectRuntimeShell } from "../../shell-utils.js";
54
63
  import { describeUnknownError, mapThinkingLevel } from "../utils.js";
55
- import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js";
56
- import { buildTtsSystemPromptHint } from "../../../tts/tts.js";
57
- import { isTimeoutError } from "../../failover-error.js";
58
- import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
59
- 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";
60
66
  import { detectAndLoadPromptImages } from "./images.js";
61
67
  export function injectHistoryImagesIntoMessages(messages, historyImagesByIndex) {
62
- if (historyImagesByIndex.size === 0)
68
+ if (historyImagesByIndex.size === 0) {
63
69
  return false;
70
+ }
64
71
  let didMutate = false;
65
72
  for (const [msgIndex, images] of historyImagesByIndex) {
66
73
  // Bounds check: ensure index is valid before accessing
67
- if (msgIndex < 0 || msgIndex >= messages.length)
74
+ if (msgIndex < 0 || msgIndex >= messages.length) {
68
75
  continue;
76
+ }
69
77
  const msg = messages[msgIndex];
70
78
  if (msg && msg.role === "user") {
71
79
  // Convert string content to array format if needed
@@ -93,6 +101,56 @@ export function injectHistoryImagesIntoMessages(messages, historyImagesByIndex)
93
101
  }
94
102
  return didMutate;
95
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
+ }
96
154
  export async function runEmbeddedAttempt(params) {
97
155
  const resolvedWorkspace = resolveUserPath(params.workspaceDir);
98
156
  const prevCwd = process.cwd();
@@ -175,6 +233,7 @@ export async function runEmbeddedAttempt(params) {
175
233
  abortSignal: runAbortController.signal,
176
234
  modelProvider: params.model.provider,
177
235
  modelId: params.modelId,
236
+ modelContextWindowTokens: params.model.contextWindow,
178
237
  modelAuthMode: resolveModelAuthMode(params.model.provider, params.config),
179
238
  currentChannelId: params.currentChannelId,
180
239
  currentThreadTs: params.currentThreadTs,
@@ -201,8 +260,9 @@ export async function runEmbeddedAttempt(params) {
201
260
  accountId: params.agentAccountId ?? undefined,
202
261
  });
203
262
  if (inlineButtonsScope !== "off") {
204
- if (!runtimeCapabilities)
263
+ if (!runtimeCapabilities) {
205
264
  runtimeCapabilities = [];
265
+ }
206
266
  if (!runtimeCapabilities.some((cap) => String(cap).trim().toLowerCase() === "inlinebuttons")) {
207
267
  runtimeCapabilities.push("inlineButtons");
208
268
  }
@@ -266,14 +326,16 @@ export async function runEmbeddedAttempt(params) {
266
326
  node: process.version,
267
327
  model: `${params.provider}/${params.modelId}`,
268
328
  defaultModel: defaultModelLabel,
329
+ shell: detectRuntimeShell(),
269
330
  channel: runtimeChannel,
270
331
  capabilities: runtimeCapabilities,
271
332
  channelActions,
272
- shell: detectRuntimeShell(),
273
333
  },
274
334
  });
275
335
  const isDefaultAgent = sessionAgentId === defaultAgentId;
276
- const promptMode = isSubagentSessionKey(params.sessionKey) ? "minimal" : "full";
336
+ const promptMode = isSubagentSessionKey(params.sessionKey) || isCronSessionKey(params.sessionKey)
337
+ ? "minimal"
338
+ : "full";
277
339
  const docsPath = await resolvePoolbotDocsPath({
278
340
  workspaceDir: effectiveWorkspace,
279
341
  argv1: process.argv[1],
@@ -317,6 +379,7 @@ export async function runEmbeddedAttempt(params) {
317
379
  model: params.modelId,
318
380
  workspaceDir: effectiveWorkspace,
319
381
  bootstrapMaxChars: resolveBootstrapMaxChars(params.config),
382
+ bootstrapTotalMaxChars: resolveBootstrapTotalMaxChars(params.config),
320
383
  sandbox: (() => {
321
384
  const runtime = resolveSandboxRuntimeStatus({
322
385
  cfg: params.config,
@@ -334,9 +397,13 @@ export async function runEmbeddedAttempt(params) {
334
397
  const systemPromptText = systemPromptOverride();
335
398
  const sessionLock = await acquireSessionWriteLock({
336
399
  sessionFile: params.sessionFile,
400
+ maxHoldMs: resolveSessionLockMaxHoldFromTimeout({
401
+ timeoutMs: params.timeoutMs,
402
+ }),
337
403
  });
338
404
  let sessionManager;
339
405
  let session;
406
+ let removeToolResultContextGuard;
340
407
  try {
341
408
  await repairSessionFileIfNeeded({
342
409
  sessionFile: params.sessionFile,
@@ -355,6 +422,7 @@ export async function runEmbeddedAttempt(params) {
355
422
  sessionManager = guardSessionManager(SessionManager.open(params.sessionFile), {
356
423
  agentId: sessionAgentId,
357
424
  sessionKey: params.sessionKey,
425
+ inputProvenance: params.inputProvenance,
358
426
  allowSyntheticToolResults: transcriptPolicy.allowSyntheticToolResults,
359
427
  });
360
428
  trackSessionManagerAccess(params.sessionFile);
@@ -370,6 +438,7 @@ export async function runEmbeddedAttempt(params) {
370
438
  settingsManager,
371
439
  minReserveTokens: resolveCompactionReserveTokensFloor(params.config),
372
440
  });
441
+ // Call for side effects (sets compaction/pruning runtime state)
373
442
  buildEmbeddedExtensionPaths({
374
443
  cfg: params.config,
375
444
  sessionManager,
@@ -377,16 +446,26 @@ export async function runEmbeddedAttempt(params) {
377
446
  modelId: params.modelId,
378
447
  model: params.model,
379
448
  });
449
+ // Get hook runner early so it's available when creating tools
450
+ const hookRunner = getGlobalHookRunner();
380
451
  const { builtInTools, customTools } = splitSdkTools({
381
452
  tools,
382
453
  sandboxEnabled: !!sandbox?.enabled,
383
454
  });
384
455
  // Add client tools (OpenResponses hosted tools) to customTools
385
456
  let clientToolCallDetected = null;
457
+ const clientToolLoopDetection = resolveToolLoopDetectionConfig({
458
+ cfg: params.config,
459
+ agentId: sessionAgentId,
460
+ });
386
461
  const clientToolDefs = params.clientTools
387
462
  ? toClientToolDefinitions(params.clientTools, (toolName, toolParams) => {
388
463
  clientToolCallDetected = { name: toolName, params: toolParams };
389
- }, { agentId: sessionAgentId, sessionKey: params.sessionKey })
464
+ }, {
465
+ agentId: sessionAgentId,
466
+ sessionKey: params.sessionKey,
467
+ loopDetection: clientToolLoopDetection,
468
+ })
390
469
  : [];
391
470
  const allCustomTools = [...customTools, ...clientToolDefs];
392
471
  ({ session } = await createAgentSession({
@@ -406,6 +485,10 @@ export async function runEmbeddedAttempt(params) {
406
485
  throw new Error("Embedded agent session missing");
407
486
  }
408
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
+ });
409
492
  const cacheTrace = createCacheTrace({
410
493
  cfg: params.config,
411
494
  env: process.env,
@@ -427,8 +510,20 @@ export async function runEmbeddedAttempt(params) {
427
510
  modelApi: params.model.api,
428
511
  workspaceDir: params.workspaceDir,
429
512
  });
430
- // Force a stable streamFn reference so vitest can reliably mock @mariozechner/pi-ai.
431
- 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
+ }
432
527
  applyExtraParamsToAgent(activeSession.agent, params.config, params.provider, params.modelId, params.streamParams);
433
528
  if (cacheTrace) {
434
529
  cacheTrace.recordStage("session:loaded", {
@@ -447,6 +542,7 @@ export async function runEmbeddedAttempt(params) {
447
542
  modelApi: params.model.api,
448
543
  modelId: params.modelId,
449
544
  provider: params.provider,
545
+ config: params.config,
450
546
  sessionManager,
451
547
  sessionId: params.sessionId,
452
548
  policy: transcriptPolicy,
@@ -458,19 +554,29 @@ export async function runEmbeddedAttempt(params) {
458
554
  const validated = transcriptPolicy.validateAnthropicTurns
459
555
  ? validateAnthropicTurns(validatedGemini)
460
556
  : validatedGemini;
461
- 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;
462
564
  cacheTrace?.recordStage("session:limited", { messages: limited });
463
565
  if (limited.length > 0) {
464
566
  activeSession.agent.replaceMessages(limited);
465
567
  }
466
568
  }
467
569
  catch (err) {
468
- sessionManager.flushPendingToolResults?.();
570
+ await flushPendingToolResultsAfterIdle({
571
+ agent: activeSession?.agent,
572
+ sessionManager,
573
+ });
469
574
  activeSession.dispose();
470
575
  throw err;
471
576
  }
472
577
  let aborted = Boolean(params.abortSignal?.aborted);
473
578
  let timedOut = false;
579
+ let timedOutDuringCompaction = false;
474
580
  const getAbortReason = (signal) => "reason" in signal ? signal.reason : undefined;
475
581
  const makeTimeoutAbortReason = () => {
476
582
  const err = new Error("request timed out");
@@ -485,8 +591,9 @@ export async function runEmbeddedAttempt(params) {
485
591
  };
486
592
  const abortRun = (isTimeout = false, reason) => {
487
593
  aborted = true;
488
- if (isTimeout)
594
+ if (isTimeout) {
489
595
  timedOut = true;
596
+ }
490
597
  if (isTimeout) {
491
598
  runAbortController.abort(reason ?? makeTimeoutAbortReason());
492
599
  }
@@ -518,6 +625,7 @@ export async function runEmbeddedAttempt(params) {
518
625
  const subscription = subscribeEmbeddedPiSession({
519
626
  session: activeSession,
520
627
  runId: params.runId,
628
+ hookRunner: getGlobalHookRunner() ?? undefined,
521
629
  verboseLevel: params.verboseLevel,
522
630
  reasoningMode: params.reasoningLevel ?? "off",
523
631
  toolResultFormat: params.toolResultFormat,
@@ -525,6 +633,7 @@ export async function runEmbeddedAttempt(params) {
525
633
  shouldEmitToolOutput: params.shouldEmitToolOutput,
526
634
  onToolResult: params.onToolResult,
527
635
  onReasoningStream: params.onReasoningStream,
636
+ onReasoningEnd: params.onReasoningEnd,
528
637
  onBlockReply: params.onBlockReply,
529
638
  onBlockReplyFlush: params.onBlockReplyFlush,
530
639
  blockReplyBreak: params.blockReplyBreak,
@@ -533,8 +642,10 @@ export async function runEmbeddedAttempt(params) {
533
642
  onAssistantMessageStart: params.onAssistantMessageStart,
534
643
  onAgentEvent: params.onAgentEvent,
535
644
  enforceFinalTag: params.enforceFinalTag,
645
+ config: params.config,
646
+ sessionKey: params.sessionKey ?? params.sessionId,
536
647
  });
537
- 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;
538
649
  const queueHandle = {
539
650
  queueMessage: async (text) => {
540
651
  await activeSession.steer(text);
@@ -543,18 +654,26 @@ export async function runEmbeddedAttempt(params) {
543
654
  isCompacting: () => subscription.isCompacting(),
544
655
  abort: abortRun,
545
656
  };
546
- setActiveEmbeddedRun(params.sessionId, queueHandle);
657
+ setActiveEmbeddedRun(params.sessionId, queueHandle, params.sessionKey);
547
658
  let abortWarnTimer;
548
659
  const isProbeSession = params.sessionId?.startsWith("probe-") ?? false;
549
660
  const abortTimer = setTimeout(() => {
550
661
  if (!isProbeSession) {
551
662
  log.warn(`embedded run timeout: runId=${params.runId} sessionId=${params.sessionId} timeoutMs=${params.timeoutMs}`);
552
663
  }
664
+ if (shouldFlagCompactionTimeout({
665
+ isTimeout: true,
666
+ isCompactionPendingOrRetrying: subscription.isCompacting(),
667
+ isCompactionInFlight: activeSession.isCompacting,
668
+ })) {
669
+ timedOutDuringCompaction = true;
670
+ }
553
671
  abortRun(true);
554
672
  if (!abortWarnTimer) {
555
673
  abortWarnTimer = setTimeout(() => {
556
- if (!activeSession.isStreaming)
674
+ if (!activeSession.isStreaming) {
557
675
  return;
676
+ }
558
677
  if (!isProbeSession) {
559
678
  log.warn(`embedded run abort still streaming: runId=${params.runId} sessionId=${params.sessionId}`);
560
679
  }
@@ -566,6 +685,13 @@ export async function runEmbeddedAttempt(params) {
566
685
  const onAbort = () => {
567
686
  const reason = params.abortSignal ? getAbortReason(params.abortSignal) : undefined;
568
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
+ }
569
695
  abortRun(timeout, reason);
570
696
  };
571
697
  if (params.abortSignal) {
@@ -578,32 +704,59 @@ export async function runEmbeddedAttempt(params) {
578
704
  });
579
705
  }
580
706
  }
581
- // Get hook runner once for both before_agent_start and agent_end hooks
582
- const hookRunner = getGlobalHookRunner();
583
- 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;
584
714
  let promptError = null;
715
+ let promptErrorSource = null;
585
716
  try {
586
717
  const promptStartedAt = Date.now();
587
- // 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.
588
720
  let effectivePrompt = params.prompt;
589
- if (hookRunner?.hasHooks("before_agent_start")) {
590
- try {
591
- const hookResult = await hookRunner.runBeforeAgentStart({
592
- prompt: params.prompt,
593
- messages: activeSession.messages,
594
- }, {
595
- agentId: hookAgentId,
596
- sessionKey: params.sessionKey,
597
- workspaceDir: params.workspaceDir,
598
- messageProvider: params.messageProvider ?? undefined,
599
- });
600
- if (hookResult?.prependContext) {
601
- effectivePrompt = `${hookResult.prependContext}\n\n${params.prompt}`;
602
- log.debug(`hooks: prepended context to prompt (${hookResult.prependContext.length} chars)`);
603
- }
604
- }
605
- catch (hookErr) {
606
- 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)`);
607
760
  }
608
761
  }
609
762
  log.debug(`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId}`);
@@ -621,7 +774,10 @@ export async function runEmbeddedAttempt(params) {
621
774
  sessionManager.resetLeaf();
622
775
  }
623
776
  const sessionContext = sessionManager.buildSessionContext();
624
- activeSession.agent.replaceMessages(sessionContext.messages);
777
+ const sanitizedOrphan = transcriptPolicy.normalizeAntigravityThinkingBlocks
778
+ ? sanitizeAntigravityThinkingBlocks(sessionContext.messages)
779
+ : sessionContext.messages;
780
+ activeSession.agent.replaceMessages(sanitizedOrphan);
625
781
  log.warn(`Removed orphaned user message to prevent consecutive user turns. ` +
626
782
  `runId=${params.runId} sessionId=${params.sessionId}`);
627
783
  }
@@ -637,8 +793,11 @@ export async function runEmbeddedAttempt(params) {
637
793
  existingImages: params.images,
638
794
  historyMessages: activeSession.messages,
639
795
  maxBytes: MAX_IMAGE_BYTES,
796
+ maxDimensionPx: resolveImageSanitizationLimits(params.config).maxDimensionPx,
640
797
  // Enforce sandbox path restrictions when sandbox is enabled
641
- sandboxRoot: sandbox?.enabled ? sandbox.workspaceDir : undefined,
798
+ sandbox: sandbox?.enabled && sandbox?.fsBridge
799
+ ? { root: sandbox.workspaceDir, bridge: sandbox.fsBridge }
800
+ : undefined,
642
801
  });
643
802
  // Inject history images into their original message positions.
644
803
  // This ensures the model sees images in context (e.g., "compare to the first image").
@@ -652,13 +811,42 @@ export async function runEmbeddedAttempt(params) {
652
811
  messages: activeSession.messages,
653
812
  note: `images: prompt=${imageResult.images.length} history=${imageResult.historyImagesByIndex.size}`,
654
813
  });
655
- const shouldTrackCacheTtl = params.config?.agents?.defaults?.contextPruning?.mode === "cache-ttl" &&
656
- isCacheTtlEligibleProvider(params.provider, params.modelId);
657
- if (shouldTrackCacheTtl) {
658
- appendCacheTtlTimestamp(sessionManager, {
659
- 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,
660
835
  provider: params.provider,
661
- 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)}`);
662
850
  });
663
851
  }
664
852
  // Only pass images option if there are actually images to pass
@@ -672,45 +860,98 @@ export async function runEmbeddedAttempt(params) {
672
860
  }
673
861
  catch (err) {
674
862
  promptError = err;
863
+ promptErrorSource = "prompt";
675
864
  }
676
865
  finally {
677
866
  log.debug(`embedded run prompt end: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - promptStartedAt}`);
678
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;
679
877
  try {
680
- await waitForCompactionRetry();
878
+ await abortable(waitForCompactionRetry());
681
879
  }
682
880
  catch (err) {
683
- if (isAbortError(err)) {
684
- if (!promptError)
881
+ if (isRunnerAbortError(err)) {
882
+ if (!promptError) {
685
883
  promptError = err;
884
+ promptErrorSource = "compaction";
885
+ }
886
+ if (!isProbeSession) {
887
+ log.debug(`compaction wait aborted: runId=${params.runId} sessionId=${params.sessionId}`);
888
+ }
686
889
  }
687
890
  else {
688
891
  throw err;
689
892
  }
690
893
  }
691
- messagesSnapshot = activeSession.messages.slice();
692
- 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
+ }
693
943
  cacheTrace?.recordStage("session:after", {
694
944
  messages: messagesSnapshot,
695
- note: promptError ? "prompt error" : undefined,
945
+ note: timedOutDuringCompaction
946
+ ? "compaction timeout"
947
+ : promptError
948
+ ? "prompt error"
949
+ : undefined,
696
950
  });
697
951
  anthropicPayloadLogger?.recordUsage(messagesSnapshot, promptError);
698
- // Record request outcome into provider infrastructure (monitoring + rate limits).
699
- // Fire-and-forget — never blocks or throws.
700
- try {
701
- recordRequestOutcome({
702
- provider: params.provider,
703
- model: params.modelId,
704
- status: promptError ? 500 : 200,
705
- latencyMs: Date.now() - promptStartedAt,
706
- streaming: true,
707
- });
708
- }
709
- catch {
710
- // Observability must never break the run
711
- }
712
952
  // Run agent_end hooks to allow plugins to analyze the conversation
713
953
  // This is fire-and-forget, so we don't await
954
+ // Run even on compaction timeout so plugins can log/cleanup
714
955
  if (hookRunner?.hasHooks("agent_end")) {
715
956
  hookRunner
716
957
  .runAgentEnd({
@@ -721,6 +962,7 @@ export async function runEmbeddedAttempt(params) {
721
962
  }, {
722
963
  agentId: hookAgentId,
723
964
  sessionKey: params.sessionKey,
965
+ sessionId: params.sessionId,
724
966
  workspaceDir: params.workspaceDir,
725
967
  messageProvider: params.messageProvider ?? undefined,
726
968
  })
@@ -731,22 +973,56 @@ export async function runEmbeddedAttempt(params) {
731
973
  }
732
974
  finally {
733
975
  clearTimeout(abortTimer);
734
- if (abortWarnTimer)
976
+ if (abortWarnTimer) {
735
977
  clearTimeout(abortWarnTimer);
736
- unsubscribe();
737
- 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);
738
992
  params.abortSignal?.removeEventListener?.("abort", onAbort);
739
993
  }
740
994
  const lastAssistant = messagesSnapshot
741
995
  .slice()
742
- .reverse()
743
- .find((m) => m?.role === "assistant");
996
+ .toReversed()
997
+ .find((m) => m.role === "assistant");
744
998
  const toolMetasNormalized = toolMetas
745
999
  .filter((entry) => typeof entry.toolName === "string" && entry.toolName.trim().length > 0)
746
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
+ }
747
1022
  return {
748
1023
  aborted,
749
1024
  timedOut,
1025
+ timedOutDuringCompaction,
750
1026
  promptError,
751
1027
  sessionIdUsed,
752
1028
  systemPromptReport,
@@ -757,15 +1033,30 @@ export async function runEmbeddedAttempt(params) {
757
1033
  lastToolError: getLastToolError?.(),
758
1034
  didSendViaMessagingTool: didSendViaMessagingTool(),
759
1035
  messagingToolSentTexts: getMessagingToolSentTexts(),
1036
+ messagingToolSentMediaUrls: getMessagingToolSentMediaUrls(),
760
1037
  messagingToolSentTargets: getMessagingToolSentTargets(),
1038
+ successfulCronAdds: getSuccessfulCronAdds(),
761
1039
  cloudCodeAssistFormatError: Boolean(lastAssistant?.errorMessage && isCloudCodeAssistFormatError(lastAssistant.errorMessage)),
1040
+ attemptUsage: getUsageTotals(),
1041
+ compactionCount: getCompactionCount(),
762
1042
  // Client tool call detected (OpenResponses hosted tools)
763
1043
  clientToolCall: clientToolCallDetected ?? undefined,
764
1044
  };
765
1045
  }
766
1046
  finally {
767
1047
  // Always tear down the session (and release the lock) before we leave this attempt.
768
- 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
+ });
769
1060
  session?.dispose();
770
1061
  await sessionLock.release();
771
1062
  }