@poolzin/pool-bot 2026.2.21 → 2026.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/device-pair/index.ts +2 -2
  326. package/extensions/diagnostics-otel/package.json +1 -1
  327. package/extensions/discord/package.json +1 -1
  328. package/extensions/feishu/package.json +1 -1
  329. package/extensions/google-antigravity-auth/package.json +1 -1
  330. package/extensions/google-gemini-cli-auth/package.json +1 -1
  331. package/extensions/googlechat/package.json +1 -1
  332. package/extensions/imessage/package.json +1 -1
  333. package/extensions/irc/package.json +1 -1
  334. package/extensions/irc/src/accounts.ts +1 -1
  335. package/extensions/irc/src/onboarding.ts +4 -4
  336. package/extensions/line/package.json +1 -1
  337. package/extensions/llm-task/package.json +1 -1
  338. package/extensions/lobster/package.json +1 -1
  339. package/extensions/matrix/CHANGELOG.md +10 -0
  340. package/extensions/matrix/package.json +1 -1
  341. package/extensions/mattermost/package.json +1 -1
  342. package/extensions/memory-core/package.json +1 -1
  343. package/extensions/memory-lancedb/package.json +1 -1
  344. package/extensions/minimax-portal-auth/package.json +1 -1
  345. package/extensions/msteams/CHANGELOG.md +10 -0
  346. package/extensions/msteams/package.json +1 -1
  347. package/extensions/nextcloud-talk/package.json +1 -1
  348. package/extensions/nostr/CHANGELOG.md +10 -0
  349. package/extensions/nostr/package.json +1 -1
  350. package/extensions/open-prose/package.json +1 -1
  351. package/extensions/openai-codex-auth/package.json +1 -1
  352. package/extensions/signal/package.json +1 -1
  353. package/extensions/slack/package.json +1 -1
  354. package/extensions/telegram/package.json +1 -1
  355. package/extensions/tlon/package.json +1 -1
  356. package/extensions/twitch/CHANGELOG.md +10 -0
  357. package/extensions/twitch/package.json +1 -1
  358. package/extensions/voice-call/CHANGELOG.md +10 -0
  359. package/extensions/voice-call/package.json +1 -1
  360. package/extensions/whatsapp/package.json +1 -1
  361. package/extensions/zalo/CHANGELOG.md +10 -0
  362. package/extensions/zalo/package.json +1 -1
  363. package/extensions/zalouser/CHANGELOG.md +10 -0
  364. package/extensions/zalouser/package.json +1 -1
  365. package/package.json +1 -1
  366. package/skills/apple-reminders/SKILL.md +100 -49
  367. package/skills/coding-agent/SKILL.md +34 -28
  368. package/skills/github/SKILL.md +131 -16
  369. package/skills/imsg/SKILL.md +112 -15
  370. package/skills/openhue/SKILL.md +101 -19
  371. package/skills/tmux/SKILL.md +111 -79
  372. package/skills/weather/SKILL.md +88 -25
  373. package/dist/agents/openclaw-tools.js +0 -151
  374. package/dist/agents/tool-security.js +0 -96
  375. package/dist/gateway/url-validation.js +0 -94
  376. package/dist/infra/openclaw-root.js +0 -109
  377. package/dist/infra/tmp-openclaw-dir.js +0 -81
  378. package/dist/media/path-sanitization.js +0 -78
@@ -0,0 +1,262 @@
1
+ const CHARS_PER_TOKEN_ESTIMATE = 4;
2
+ // Keep a conservative input budget to absorb tokenizer variance and provider framing overhead.
3
+ const CONTEXT_INPUT_HEADROOM_RATIO = 0.75;
4
+ const SINGLE_TOOL_RESULT_CONTEXT_SHARE = 0.5;
5
+ const TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE = 2;
6
+ const IMAGE_CHAR_ESTIMATE = 8_000;
7
+ export const CONTEXT_LIMIT_TRUNCATION_NOTICE = "[truncated: output exceeded context limit]";
8
+ const CONTEXT_LIMIT_TRUNCATION_SUFFIX = `\n${CONTEXT_LIMIT_TRUNCATION_NOTICE}`;
9
+ export const PREEMPTIVE_TOOL_RESULT_COMPACTION_PLACEHOLDER = "[compacted: tool output removed to free context]";
10
+ function isTextBlock(block) {
11
+ return !!block && typeof block === "object" && block.type === "text";
12
+ }
13
+ function isImageBlock(block) {
14
+ return !!block && typeof block === "object" && block.type === "image";
15
+ }
16
+ function estimateUnknownChars(value) {
17
+ if (typeof value === "string") {
18
+ return value.length;
19
+ }
20
+ if (value === undefined) {
21
+ return 0;
22
+ }
23
+ try {
24
+ const serialized = JSON.stringify(value);
25
+ return typeof serialized === "string" ? serialized.length : 0;
26
+ }
27
+ catch {
28
+ return 256;
29
+ }
30
+ }
31
+ function isToolResultMessage(msg) {
32
+ const role = msg.role;
33
+ const type = msg.type;
34
+ return role === "toolResult" || role === "tool" || type === "toolResult";
35
+ }
36
+ function getToolResultContent(msg) {
37
+ if (!isToolResultMessage(msg)) {
38
+ return [];
39
+ }
40
+ const content = msg.content;
41
+ if (typeof content === "string") {
42
+ return [{ type: "text", text: content }];
43
+ }
44
+ return Array.isArray(content) ? content : [];
45
+ }
46
+ function getToolResultText(msg) {
47
+ const content = getToolResultContent(msg);
48
+ const chunks = [];
49
+ for (const block of content) {
50
+ if (isTextBlock(block)) {
51
+ chunks.push(block.text);
52
+ }
53
+ }
54
+ return chunks.join("\n");
55
+ }
56
+ function estimateMessageChars(msg) {
57
+ if (!msg || typeof msg !== "object") {
58
+ return 0;
59
+ }
60
+ if (msg.role === "user") {
61
+ const content = msg.content;
62
+ if (typeof content === "string") {
63
+ return content.length;
64
+ }
65
+ let chars = 0;
66
+ if (Array.isArray(content)) {
67
+ for (const block of content) {
68
+ if (isTextBlock(block)) {
69
+ chars += block.text.length;
70
+ }
71
+ else if (isImageBlock(block)) {
72
+ chars += IMAGE_CHAR_ESTIMATE;
73
+ }
74
+ else {
75
+ chars += estimateUnknownChars(block);
76
+ }
77
+ }
78
+ }
79
+ return chars;
80
+ }
81
+ if (msg.role === "assistant") {
82
+ let chars = 0;
83
+ const content = msg.content;
84
+ if (Array.isArray(content)) {
85
+ for (const block of content) {
86
+ if (!block || typeof block !== "object") {
87
+ continue;
88
+ }
89
+ const typed = block;
90
+ if (typed.type === "text" && typeof typed.text === "string") {
91
+ chars += typed.text.length;
92
+ }
93
+ else if (typed.type === "thinking" && typeof typed.thinking === "string") {
94
+ chars += typed.thinking.length;
95
+ }
96
+ else if (typed.type === "toolCall") {
97
+ try {
98
+ chars += JSON.stringify(typed.arguments ?? {}).length;
99
+ }
100
+ catch {
101
+ chars += 128;
102
+ }
103
+ }
104
+ else {
105
+ chars += estimateUnknownChars(block);
106
+ }
107
+ }
108
+ }
109
+ return chars;
110
+ }
111
+ if (isToolResultMessage(msg)) {
112
+ let chars = 0;
113
+ const content = getToolResultContent(msg);
114
+ for (const block of content) {
115
+ if (isTextBlock(block)) {
116
+ chars += block.text.length;
117
+ }
118
+ else if (isImageBlock(block)) {
119
+ chars += IMAGE_CHAR_ESTIMATE;
120
+ }
121
+ else {
122
+ chars += estimateUnknownChars(block);
123
+ }
124
+ }
125
+ const details = msg.details;
126
+ chars += estimateUnknownChars(details);
127
+ const weightedChars = Math.ceil(chars * (CHARS_PER_TOKEN_ESTIMATE / TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE));
128
+ return Math.max(chars, weightedChars);
129
+ }
130
+ return 256;
131
+ }
132
+ function estimateContextChars(messages) {
133
+ return messages.reduce((sum, msg) => sum + estimateMessageChars(msg), 0);
134
+ }
135
+ function truncateTextToBudget(text, maxChars) {
136
+ if (text.length <= maxChars) {
137
+ return text;
138
+ }
139
+ if (maxChars <= 0) {
140
+ return CONTEXT_LIMIT_TRUNCATION_NOTICE;
141
+ }
142
+ const bodyBudget = Math.max(0, maxChars - CONTEXT_LIMIT_TRUNCATION_SUFFIX.length);
143
+ if (bodyBudget <= 0) {
144
+ return CONTEXT_LIMIT_TRUNCATION_NOTICE;
145
+ }
146
+ let cutPoint = bodyBudget;
147
+ const newline = text.lastIndexOf("\n", bodyBudget);
148
+ if (newline > bodyBudget * 0.7) {
149
+ cutPoint = newline;
150
+ }
151
+ return text.slice(0, cutPoint) + CONTEXT_LIMIT_TRUNCATION_SUFFIX;
152
+ }
153
+ function replaceToolResultText(msg, text) {
154
+ const content = msg.content;
155
+ const replacementContent = typeof content === "string" || content === undefined ? text : [{ type: "text", text }];
156
+ const sourceRecord = msg;
157
+ const { details: _details, ...rest } = sourceRecord;
158
+ return {
159
+ ...rest,
160
+ content: replacementContent,
161
+ };
162
+ }
163
+ function truncateToolResultToChars(msg, maxChars) {
164
+ if (!isToolResultMessage(msg)) {
165
+ return msg;
166
+ }
167
+ const estimatedChars = estimateMessageChars(msg);
168
+ if (estimatedChars <= maxChars) {
169
+ return msg;
170
+ }
171
+ const rawText = getToolResultText(msg);
172
+ if (!rawText) {
173
+ return replaceToolResultText(msg, CONTEXT_LIMIT_TRUNCATION_NOTICE);
174
+ }
175
+ const truncatedText = truncateTextToBudget(rawText, maxChars);
176
+ return replaceToolResultText(msg, truncatedText);
177
+ }
178
+ function compactExistingToolResultsInPlace(params) {
179
+ const { messages, charsNeeded } = params;
180
+ if (charsNeeded <= 0) {
181
+ return 0;
182
+ }
183
+ let reduced = 0;
184
+ for (let i = 0; i < messages.length; i++) {
185
+ const msg = messages[i];
186
+ if (!isToolResultMessage(msg)) {
187
+ continue;
188
+ }
189
+ const before = estimateMessageChars(msg);
190
+ if (before <= PREEMPTIVE_TOOL_RESULT_COMPACTION_PLACEHOLDER.length) {
191
+ continue;
192
+ }
193
+ const compacted = replaceToolResultText(msg, PREEMPTIVE_TOOL_RESULT_COMPACTION_PLACEHOLDER);
194
+ applyMessageMutationInPlace(msg, compacted);
195
+ const after = estimateMessageChars(msg);
196
+ if (after >= before) {
197
+ continue;
198
+ }
199
+ reduced += before - after;
200
+ if (reduced >= charsNeeded) {
201
+ break;
202
+ }
203
+ }
204
+ return reduced;
205
+ }
206
+ function applyMessageMutationInPlace(target, source) {
207
+ if (target === source) {
208
+ return;
209
+ }
210
+ const targetRecord = target;
211
+ const sourceRecord = source;
212
+ for (const key of Object.keys(targetRecord)) {
213
+ if (!(key in sourceRecord)) {
214
+ delete targetRecord[key];
215
+ }
216
+ }
217
+ Object.assign(targetRecord, sourceRecord);
218
+ }
219
+ function enforceToolResultContextBudgetInPlace(params) {
220
+ const { messages, contextBudgetChars, maxSingleToolResultChars } = params;
221
+ // Ensure each tool result has an upper bound before considering total context usage.
222
+ for (const message of messages) {
223
+ if (!isToolResultMessage(message)) {
224
+ continue;
225
+ }
226
+ const truncated = truncateToolResultToChars(message, maxSingleToolResultChars);
227
+ applyMessageMutationInPlace(message, truncated);
228
+ }
229
+ let currentChars = estimateContextChars(messages);
230
+ if (currentChars <= contextBudgetChars) {
231
+ return;
232
+ }
233
+ // Compact oldest tool outputs first until the context is back under budget.
234
+ compactExistingToolResultsInPlace({
235
+ messages,
236
+ charsNeeded: currentChars - contextBudgetChars,
237
+ });
238
+ }
239
+ export function installToolResultContextGuard(params) {
240
+ const contextWindowTokens = Math.max(1, Math.floor(params.contextWindowTokens));
241
+ const contextBudgetChars = Math.max(1_024, Math.floor(contextWindowTokens * CHARS_PER_TOKEN_ESTIMATE * CONTEXT_INPUT_HEADROOM_RATIO));
242
+ const maxSingleToolResultChars = Math.max(1_024, Math.floor(contextWindowTokens * TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE * SINGLE_TOOL_RESULT_CONTEXT_SHARE));
243
+ // Agent.transformContext is private in pi-coding-agent, so access it via a
244
+ // narrow runtime view to keep callsites type-safe while preserving behavior.
245
+ const mutableAgent = params.agent;
246
+ const originalTransformContext = mutableAgent.transformContext;
247
+ mutableAgent.transformContext = (async (messages, signal) => {
248
+ const transformed = originalTransformContext
249
+ ? await originalTransformContext.call(mutableAgent, messages, signal)
250
+ : messages;
251
+ const contextMessages = Array.isArray(transformed) ? transformed : messages;
252
+ enforceToolResultContextBudgetInPlace({
253
+ messages: contextMessages,
254
+ contextBudgetChars,
255
+ maxSingleToolResultChars,
256
+ });
257
+ return contextMessages;
258
+ });
259
+ return () => {
260
+ mutableAgent.transformContext = originalTransformContext;
261
+ };
262
+ }
@@ -1,7 +1,7 @@
1
1
  export { compactEmbeddedPiSession } from "./pi-embedded-runner/compact.js";
2
2
  export { applyExtraParamsToAgent, resolveExtraParams } from "./pi-embedded-runner/extra-params.js";
3
3
  export { applyGoogleTurnOrderingFix } from "./pi-embedded-runner/google.js";
4
- export { getDmHistoryLimitFromSessionKey, limitHistoryTurns, } from "./pi-embedded-runner/history.js";
4
+ export { getDmHistoryLimitFromSessionKey, getHistoryLimitFromSessionKey, limitHistoryTurns, } from "./pi-embedded-runner/history.js";
5
5
  export { resolveEmbeddedSessionLane } from "./pi-embedded-runner/lanes.js";
6
6
  export { runEmbeddedPiAgent } from "./pi-embedded-runner/run.js";
7
7
  export { abortEmbeddedPiRun, isEmbeddedPiRunActive, isEmbeddedPiRunStreaming, queueEmbeddedPiMessage, waitForEmbeddedPiRunEnd, } from "./pi-embedded-runner/runs.js";
@@ -1,26 +1,107 @@
1
1
  import { emitAgentEvent } from "../infra/agent-events.js";
2
+ import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
2
3
  import { normalizeTextForComparison } from "./pi-embedded-helpers.js";
3
4
  import { isMessagingTool, isMessagingToolSendAction } from "./pi-embedded-messaging.js";
4
- import { extractToolErrorMessage, extractToolResultText, extractMessagingToolSend, isToolResultError, sanitizeToolResult, } from "./pi-embedded-subscribe.tools.js";
5
+ import { extractToolErrorMessage, extractToolResultMediaPaths, extractToolResultText, extractMessagingToolSend, isToolResultError, sanitizeToolResult, } from "./pi-embedded-subscribe.tools.js";
5
6
  import { inferToolMetaFromArgs } from "./pi-embedded-utils.js";
7
+ import { buildToolMutationState, isSameToolMutationAction } from "./tool-mutation.js";
6
8
  import { normalizeToolName } from "./tool-policy.js";
9
+ /** Track tool execution start times and args for after_tool_call hook */
10
+ const toolStartData = new Map();
11
+ function isCronAddAction(args) {
12
+ if (!args || typeof args !== "object") {
13
+ return false;
14
+ }
15
+ const action = args.action;
16
+ return typeof action === "string" && action.trim().toLowerCase() === "add";
17
+ }
18
+ function buildToolCallSummary(toolName, args, meta) {
19
+ const mutation = buildToolMutationState(toolName, args, meta);
20
+ return {
21
+ meta,
22
+ mutatingAction: mutation.mutatingAction,
23
+ actionFingerprint: mutation.actionFingerprint,
24
+ };
25
+ }
7
26
  function extendExecMeta(toolName, args, meta) {
8
27
  const normalized = toolName.trim().toLowerCase();
9
- if (normalized !== "exec" && normalized !== "bash")
28
+ if (normalized !== "exec" && normalized !== "bash") {
10
29
  return meta;
11
- if (!args || typeof args !== "object")
30
+ }
31
+ if (!args || typeof args !== "object") {
12
32
  return meta;
33
+ }
13
34
  const record = args;
14
35
  const flags = [];
15
- if (record.pty === true)
36
+ if (record.pty === true) {
16
37
  flags.push("pty");
17
- if (record.elevated === true)
38
+ }
39
+ if (record.elevated === true) {
18
40
  flags.push("elevated");
19
- if (flags.length === 0)
41
+ }
42
+ if (flags.length === 0) {
20
43
  return meta;
44
+ }
21
45
  const suffix = flags.join(" · ");
22
46
  return meta ? `${meta} · ${suffix}` : suffix;
23
47
  }
48
+ function pushUniqueMediaUrl(urls, seen, value) {
49
+ if (typeof value !== "string") {
50
+ return;
51
+ }
52
+ const normalized = value.trim();
53
+ if (!normalized || seen.has(normalized)) {
54
+ return;
55
+ }
56
+ seen.add(normalized);
57
+ urls.push(normalized);
58
+ }
59
+ function collectMessagingMediaUrlsFromRecord(record) {
60
+ const urls = [];
61
+ const seen = new Set();
62
+ pushUniqueMediaUrl(urls, seen, record.media);
63
+ pushUniqueMediaUrl(urls, seen, record.mediaUrl);
64
+ pushUniqueMediaUrl(urls, seen, record.path);
65
+ pushUniqueMediaUrl(urls, seen, record.filePath);
66
+ const mediaUrls = record.mediaUrls;
67
+ if (Array.isArray(mediaUrls)) {
68
+ for (const mediaUrl of mediaUrls) {
69
+ pushUniqueMediaUrl(urls, seen, mediaUrl);
70
+ }
71
+ }
72
+ return urls;
73
+ }
74
+ function collectMessagingMediaUrlsFromToolResult(result) {
75
+ const urls = [];
76
+ const seen = new Set();
77
+ const appendFromRecord = (value) => {
78
+ if (!value || typeof value !== "object") {
79
+ return;
80
+ }
81
+ const extracted = collectMessagingMediaUrlsFromRecord(value);
82
+ for (const url of extracted) {
83
+ if (seen.has(url)) {
84
+ continue;
85
+ }
86
+ seen.add(url);
87
+ urls.push(url);
88
+ }
89
+ };
90
+ appendFromRecord(result);
91
+ if (result && typeof result === "object") {
92
+ appendFromRecord(result.details);
93
+ }
94
+ const outputText = extractToolResultText(result);
95
+ if (outputText) {
96
+ try {
97
+ appendFromRecord(JSON.parse(outputText));
98
+ }
99
+ catch {
100
+ // Ignore non-JSON tool output.
101
+ }
102
+ }
103
+ return urls;
104
+ }
24
105
  export async function handleToolExecutionStart(ctx, evt) {
25
106
  // Flush pending block replies to preserve message boundaries before tool execution.
26
107
  ctx.flushBlockReplyBuffer();
@@ -31,16 +112,23 @@ export async function handleToolExecutionStart(ctx, evt) {
31
112
  const toolName = normalizeToolName(rawToolName);
32
113
  const toolCallId = String(evt.toolCallId);
33
114
  const args = evt.args;
115
+ // Track start time and args for after_tool_call hook
116
+ toolStartData.set(toolCallId, { startTime: Date.now(), args });
34
117
  if (toolName === "read") {
35
118
  const record = args && typeof args === "object" ? args : {};
36
- const filePath = typeof record.path === "string" ? record.path.trim() : "";
119
+ const filePathValue = typeof record.path === "string"
120
+ ? record.path
121
+ : typeof record.file_path === "string"
122
+ ? record.file_path
123
+ : "";
124
+ const filePath = filePathValue.trim();
37
125
  if (!filePath) {
38
126
  const argsPreview = typeof args === "string" ? args.slice(0, 200) : undefined;
39
127
  ctx.log.warn(`read tool called without path: toolCallId=${toolCallId} argsType=${typeof args}${argsPreview ? ` argsPreview=${argsPreview}` : ""}`);
40
128
  }
41
129
  }
42
130
  const meta = extendExecMeta(toolName, args, inferToolMetaFromArgs(toolName, args));
43
- ctx.state.toolMetaById.set(toolCallId, meta);
131
+ ctx.state.toolMetaById.set(toolCallId, buildToolCallSummary(toolName, args, meta));
44
132
  ctx.log.debug(`embedded run tool start: runId=${ctx.params.runId} tool=${toolName} toolCallId=${toolCallId}`);
45
133
  const shouldEmitToolEvents = ctx.shouldEmitToolResult();
46
134
  emitAgentEvent({
@@ -79,6 +167,11 @@ export async function handleToolExecutionStart(ctx, evt) {
79
167
  ctx.state.pendingMessagingTexts.set(toolCallId, text);
80
168
  ctx.log.debug(`Tracking pending messaging text: tool=${toolName} len=${text.length}`);
81
169
  }
170
+ // Track media URLs from messaging tool args (pending until tool_execution_end).
171
+ const mediaUrls = collectMessagingMediaUrlsFromRecord(argsRecord);
172
+ if (mediaUrls.length > 0) {
173
+ ctx.state.pendingMessagingMediaUrls.set(toolCallId, mediaUrls);
174
+ }
82
175
  }
83
176
  }
84
177
  }
@@ -106,14 +199,17 @@ export function handleToolExecutionUpdate(ctx, evt) {
106
199
  },
107
200
  });
108
201
  }
109
- export function handleToolExecutionEnd(ctx, evt) {
202
+ export async function handleToolExecutionEnd(ctx, evt) {
110
203
  const toolName = normalizeToolName(String(evt.toolName));
111
204
  const toolCallId = String(evt.toolCallId);
112
205
  const isError = Boolean(evt.isError);
113
206
  const result = evt.result;
114
207
  const isToolError = isError || isToolResultError(result);
115
208
  const sanitizedResult = sanitizeToolResult(result);
116
- const meta = ctx.state.toolMetaById.get(toolCallId);
209
+ const startData = toolStartData.get(toolCallId);
210
+ toolStartData.delete(toolCallId);
211
+ const callSummary = ctx.state.toolMetaById.get(toolCallId);
212
+ const meta = callSummary?.meta;
117
213
  ctx.state.toolMetas.push({ toolName, meta });
118
214
  ctx.state.toolMetaById.delete(toolCallId);
119
215
  ctx.state.toolSummaryById.delete(toolCallId);
@@ -123,8 +219,25 @@ export function handleToolExecutionEnd(ctx, evt) {
123
219
  toolName,
124
220
  meta,
125
221
  error: errorMessage,
222
+ mutatingAction: callSummary?.mutatingAction,
223
+ actionFingerprint: callSummary?.actionFingerprint,
126
224
  };
127
225
  }
226
+ else if (ctx.state.lastToolError) {
227
+ // Keep unresolved mutating failures until the same action succeeds.
228
+ if (ctx.state.lastToolError.mutatingAction) {
229
+ if (isSameToolMutationAction(ctx.state.lastToolError, {
230
+ toolName,
231
+ meta,
232
+ actionFingerprint: callSummary?.actionFingerprint,
233
+ })) {
234
+ ctx.state.lastToolError = undefined;
235
+ }
236
+ }
237
+ else {
238
+ ctx.state.lastToolError = undefined;
239
+ }
240
+ }
128
241
  // Commit messaging tool text on success, discard on error.
129
242
  const pendingText = ctx.state.pendingMessagingTexts.get(toolCallId);
130
243
  const pendingTarget = ctx.state.pendingMessagingTargets.get(toolCallId);
@@ -144,6 +257,27 @@ export function handleToolExecutionEnd(ctx, evt) {
144
257
  ctx.trimMessagingToolSent();
145
258
  }
146
259
  }
260
+ const pendingMediaUrls = ctx.state.pendingMessagingMediaUrls.get(toolCallId) ?? [];
261
+ ctx.state.pendingMessagingMediaUrls.delete(toolCallId);
262
+ const startArgs = startData?.args && typeof startData.args === "object"
263
+ ? startData.args
264
+ : {};
265
+ const isMessagingSend = pendingMediaUrls.length > 0 ||
266
+ (isMessagingTool(toolName) && isMessagingToolSendAction(toolName, startArgs));
267
+ if (!isToolError && isMessagingSend) {
268
+ const committedMediaUrls = [
269
+ ...pendingMediaUrls,
270
+ ...collectMessagingMediaUrlsFromToolResult(result),
271
+ ];
272
+ if (committedMediaUrls.length > 0) {
273
+ ctx.state.messagingToolSentMediaUrls.push(...committedMediaUrls);
274
+ ctx.trimMessagingToolSent();
275
+ }
276
+ }
277
+ // Track committed reminders only when cron.add completed successfully.
278
+ if (!isToolError && toolName === "cron" && isCronAddAction(startData?.args)) {
279
+ ctx.state.successfulCronAdds += 1;
280
+ }
147
281
  emitAgentEvent({
148
282
  runId: ctx.params.runId,
149
283
  stream: "tool",
@@ -173,4 +307,40 @@ export function handleToolExecutionEnd(ctx, evt) {
173
307
  ctx.emitToolOutput(toolName, meta, outputText);
174
308
  }
175
309
  }
310
+ // Deliver media from tool results when the verbose emitToolOutput path is off.
311
+ // When shouldEmitToolOutput() is true, emitToolOutput already delivers media
312
+ // via parseReplyDirectives (MEDIA: text extraction), so skip to avoid duplicates.
313
+ if (ctx.params.onToolResult && !isToolError && !ctx.shouldEmitToolOutput()) {
314
+ const mediaPaths = extractToolResultMediaPaths(result);
315
+ if (mediaPaths.length > 0) {
316
+ try {
317
+ void ctx.params.onToolResult({ mediaUrls: mediaPaths });
318
+ }
319
+ catch {
320
+ // ignore delivery failures
321
+ }
322
+ }
323
+ }
324
+ // Run after_tool_call plugin hook (fire-and-forget)
325
+ const hookRunnerAfter = ctx.hookRunner ?? getGlobalHookRunner();
326
+ if (hookRunnerAfter?.hasHooks("after_tool_call")) {
327
+ const durationMs = startData?.startTime != null ? Date.now() - startData.startTime : undefined;
328
+ const toolArgs = startData?.args;
329
+ const hookEvent = {
330
+ toolName,
331
+ params: (toolArgs && typeof toolArgs === "object" ? toolArgs : {}),
332
+ result: sanitizedResult,
333
+ error: isToolError ? extractToolErrorMessage(sanitizedResult) : undefined,
334
+ durationMs,
335
+ };
336
+ void hookRunnerAfter
337
+ .runAfterToolCall(hookEvent, {
338
+ toolName,
339
+ agentId: undefined,
340
+ sessionKey: undefined,
341
+ })
342
+ .catch((err) => {
343
+ ctx.log.warn(`after_tool_call hook failed: tool=${toolName} error=${String(err)}`);
344
+ });
345
+ }
176
346
  }
@@ -53,8 +53,11 @@ export function subscribeEmbeddedPiSession(params) {
53
53
  messagingToolSentTexts: [],
54
54
  messagingToolSentTextsNormalized: [],
55
55
  messagingToolSentTargets: [],
56
+ messagingToolSentMediaUrls: [],
56
57
  pendingMessagingTexts: new Map(),
57
58
  pendingMessagingTargets: new Map(),
59
+ successfulCronAdds: 0,
60
+ pendingMessagingMediaUrls: new Map(),
58
61
  };
59
62
  const usageTotals = {
60
63
  input: 0,
@@ -71,6 +74,7 @@ export function subscribeEmbeddedPiSession(params) {
71
74
  const messagingToolSentTexts = state.messagingToolSentTexts;
72
75
  const messagingToolSentTextsNormalized = state.messagingToolSentTextsNormalized;
73
76
  const messagingToolSentTargets = state.messagingToolSentTargets;
77
+ const messagingToolSentMediaUrls = state.messagingToolSentMediaUrls;
74
78
  const pendingMessagingTexts = state.pendingMessagingTexts;
75
79
  const pendingMessagingTargets = state.pendingMessagingTargets;
76
80
  const replyDirectiveAccumulator = createStreamingDirectiveAccumulator();
@@ -158,6 +162,7 @@ export function subscribeEmbeddedPiSession(params) {
158
162
  // These tools can send messages via sendMessage/threadReply actions (or sessions_send with message).
159
163
  const MAX_MESSAGING_SENT_TEXTS = 200;
160
164
  const MAX_MESSAGING_SENT_TARGETS = 200;
165
+ const MAX_MESSAGING_SENT_MEDIA_URLS = 200;
161
166
  const trimMessagingToolSent = () => {
162
167
  if (messagingToolSentTexts.length > MAX_MESSAGING_SENT_TEXTS) {
163
168
  const overflow = messagingToolSentTexts.length - MAX_MESSAGING_SENT_TEXTS;
@@ -168,13 +173,20 @@ export function subscribeEmbeddedPiSession(params) {
168
173
  const overflow = messagingToolSentTargets.length - MAX_MESSAGING_SENT_TARGETS;
169
174
  messagingToolSentTargets.splice(0, overflow);
170
175
  }
176
+ if (messagingToolSentMediaUrls.length > MAX_MESSAGING_SENT_MEDIA_URLS) {
177
+ const overflow = messagingToolSentMediaUrls.length - MAX_MESSAGING_SENT_MEDIA_URLS;
178
+ messagingToolSentMediaUrls.splice(0, overflow);
179
+ }
171
180
  };
172
181
  const ensureCompactionPromise = () => {
173
182
  if (!state.compactionRetryPromise) {
183
+ // Create a single promise that resolves when ALL pending compactions complete
184
+ // (tracked by pendingCompactionRetry counter, decremented in resolveCompactionRetry)
174
185
  state.compactionRetryPromise = new Promise((resolve, reject) => {
175
186
  state.compactionRetryResolve = resolve;
176
187
  state.compactionRetryReject = reject;
177
188
  });
189
+ // Prevent unhandled rejection if rejected after all consumers have resolved
178
190
  state.compactionRetryPromise.catch((err) => {
179
191
  log.debug(`compaction promise rejected (no waiter): ${String(err)}`);
180
192
  });
@@ -469,9 +481,12 @@ export function subscribeEmbeddedPiSession(params) {
469
481
  if (formatted === state.lastStreamedReasoning) {
470
482
  return;
471
483
  }
484
+ // Compute delta: new text since the last emitted reasoning.
485
+ // Guard against non-prefix changes (e.g. trim/format altering earlier content).
472
486
  const prior = state.lastStreamedReasoning ?? "";
473
487
  const delta = formatted.startsWith(prior) ? formatted.slice(prior.length) : formatted;
474
488
  state.lastStreamedReasoning = formatted;
489
+ // Broadcast thinking event to WebSocket clients in real-time
475
490
  emitAgentEvent({
476
491
  runId: params.runId,
477
492
  stream: "thinking",
@@ -493,8 +508,11 @@ export function subscribeEmbeddedPiSession(params) {
493
508
  messagingToolSentTexts.length = 0;
494
509
  messagingToolSentTextsNormalized.length = 0;
495
510
  messagingToolSentTargets.length = 0;
511
+ messagingToolSentMediaUrls.length = 0;
496
512
  pendingMessagingTexts.clear();
497
513
  pendingMessagingTargets.clear();
514
+ state.successfulCronAdds = 0;
515
+ state.pendingMessagingMediaUrls.clear();
498
516
  resetAssistantMessageState(0);
499
517
  };
500
518
  const noteLastAssistant = (msg) => {
@@ -538,17 +556,33 @@ export function subscribeEmbeddedPiSession(params) {
538
556
  if (state.unsubscribed) {
539
557
  return;
540
558
  }
559
+ // Mark as unsubscribed FIRST to prevent waitForCompactionRetry from creating
560
+ // new un-resolvable promises during teardown.
541
561
  state.unsubscribed = true;
562
+ // Reject pending compaction wait to unblock awaiting code.
563
+ // Don't resolve, as that would incorrectly signal "compaction complete" when it's still in-flight.
542
564
  if (state.compactionRetryPromise) {
543
565
  log.debug(`unsubscribe: rejecting compaction wait runId=${params.runId}`);
544
566
  const reject = state.compactionRetryReject;
545
567
  state.compactionRetryResolve = undefined;
546
568
  state.compactionRetryReject = undefined;
547
569
  state.compactionRetryPromise = null;
570
+ // Reject with AbortError so it's caught by isAbortError() check in cleanup paths
548
571
  const abortErr = new Error("Unsubscribed during compaction");
549
572
  abortErr.name = "AbortError";
550
573
  reject?.(abortErr);
551
574
  }
575
+ // Cancel any in-flight compaction to prevent resource leaks when unsubscribing.
576
+ // Only abort if compaction is actually running to avoid unnecessary work.
577
+ if (params.session.isCompacting) {
578
+ log.debug(`unsubscribe: aborting in-flight compaction runId=${params.runId}`);
579
+ try {
580
+ params.session.abortCompaction();
581
+ }
582
+ catch (err) {
583
+ log.warn(`unsubscribe: compaction abort failed runId=${params.runId} err=${String(err)}`);
584
+ }
585
+ }
552
586
  sessionUnsubscribe();
553
587
  };
554
588
  return {
@@ -558,7 +592,9 @@ export function subscribeEmbeddedPiSession(params) {
558
592
  isCompacting: () => state.compactionInFlight || state.pendingCompactionRetry > 0,
559
593
  isCompactionInFlight: () => state.compactionInFlight,
560
594
  getMessagingToolSentTexts: () => messagingToolSentTexts.slice(),
595
+ getMessagingToolSentMediaUrls: () => messagingToolSentMediaUrls.slice(),
561
596
  getMessagingToolSentTargets: () => messagingToolSentTargets.slice(),
597
+ getSuccessfulCronAdds: () => state.successfulCronAdds,
562
598
  // Returns true if any messaging tool successfully sent a message.
563
599
  // Used to suppress agent's confirmation text (e.g., "Respondi no Telegram!")
564
600
  // which is generated AFTER the tool sends the actual answer.
@@ -567,6 +603,7 @@ export function subscribeEmbeddedPiSession(params) {
567
603
  getUsageTotals,
568
604
  getCompactionCount: () => compactionCount,
569
605
  waitForCompactionRetry: () => {
606
+ // Reject after unsubscribe so callers treat it as cancellation, not success
570
607
  if (state.unsubscribed) {
571
608
  const err = new Error("Unsubscribed during compaction wait");
572
609
  err.name = "AbortError";