@poolzin/pool-bot 2026.2.24 → 2026.2.26

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 (646) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/acp/client.js +207 -18
  3. package/dist/acp/event-mapper.js +87 -22
  4. package/dist/acp/meta.js +12 -6
  5. package/dist/acp/secret-file.js +22 -0
  6. package/dist/agents/agent-paths.js +8 -9
  7. package/dist/agents/agent-scope.js +17 -5
  8. package/dist/agents/auth-profiles/oauth.js +148 -64
  9. package/dist/agents/auth-profiles/session-override.js +13 -7
  10. package/dist/agents/bash-process-registry.test-helpers.js +29 -0
  11. package/dist/agents/bash-tools.exec-approval-request.js +20 -0
  12. package/dist/agents/bash-tools.exec-host-gateway.js +240 -0
  13. package/dist/agents/bash-tools.exec-host-node.js +235 -0
  14. package/dist/agents/bash-tools.exec-runtime.js +2 -25
  15. package/dist/agents/bash-tools.exec-types.js +1 -0
  16. package/dist/agents/bash-tools.process.js +224 -218
  17. package/dist/agents/bedrock-discovery.js +3 -1
  18. package/dist/agents/byteplus-models.js +97 -0
  19. package/dist/agents/chutes-oauth.js +1 -0
  20. package/dist/agents/cli-runner/helpers.js +4 -0
  21. package/dist/agents/compaction.js +41 -14
  22. package/dist/agents/content-blocks.js +16 -0
  23. package/dist/agents/doubao-models.js +121 -0
  24. package/dist/agents/failover-error.js +2 -0
  25. package/dist/agents/huggingface-models.js +5 -3
  26. package/dist/agents/live-model-filter.js +5 -0
  27. package/dist/agents/minimax-vlm.js +10 -8
  28. package/dist/agents/model-auth.js +6 -0
  29. package/dist/agents/model-catalog.js +3 -1
  30. package/dist/agents/model-fallback.js +96 -101
  31. package/dist/agents/model-selection.js +7 -1
  32. package/dist/agents/models-config.providers.js +364 -165
  33. package/dist/agents/ollama-stream.js +117 -4
  34. package/dist/agents/opencode-zen-models.js +22 -11
  35. package/dist/agents/pi-embedded-helpers/errors.js +55 -33
  36. package/dist/agents/pi-embedded-helpers/messaging-dedupe.js +10 -5
  37. package/dist/agents/pi-embedded-helpers/thinking.js +10 -5
  38. package/dist/agents/pi-embedded-helpers.js +1 -1
  39. package/dist/agents/pi-embedded-payloads.js +1 -0
  40. package/dist/agents/pi-embedded-runner/compact.js +29 -7
  41. package/dist/agents/pi-embedded-runner/extensions.js +28 -26
  42. package/dist/agents/pi-embedded-runner/google.js +20 -8
  43. package/dist/agents/pi-embedded-runner/run/attempt.js +95 -36
  44. package/dist/agents/pi-embedded-runner/run.js +71 -12
  45. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
  46. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +11 -2
  47. package/dist/agents/pi-embedded-runner/session-manager-cache.js +11 -7
  48. package/dist/agents/pi-embedded-runner/system-prompt.js +2 -0
  49. package/dist/agents/pi-embedded-runner/thinking.js +42 -0
  50. package/dist/agents/pi-embedded-runner/tool-name-allowlist.js +19 -0
  51. package/dist/agents/pi-embedded-runner/utils.js +7 -10
  52. package/dist/agents/pi-embedded-subscribe.handlers.lifecycle.js +45 -56
  53. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +2 -2
  54. package/dist/agents/pi-embedded-subscribe.js +9 -4
  55. package/dist/agents/pi-embedded-subscribe.tools.js +68 -14
  56. package/dist/agents/pi-embedded-utils.js +3 -0
  57. package/dist/agents/pi-extensions/compaction-safeguard-runtime.js +4 -20
  58. package/dist/agents/pi-extensions/compaction-safeguard.js +75 -33
  59. package/dist/agents/pi-settings.js +40 -0
  60. package/dist/agents/pi-tools.policy.js +2 -1
  61. package/dist/agents/provider/config-loader.js +1 -1
  62. package/dist/agents/sandbox/browser.js +170 -33
  63. package/dist/agents/sandbox/config-hash.js +14 -27
  64. package/dist/agents/sandbox/config.js +21 -2
  65. package/dist/agents/sandbox/constants.js +2 -0
  66. package/dist/agents/sandbox/docker.js +16 -2
  67. package/dist/agents/sandbox/novnc-auth.js +62 -0
  68. package/dist/agents/sandbox/sanitize-env-vars.js +1 -1
  69. package/dist/agents/sandbox/shared.js +10 -6
  70. package/dist/agents/sandbox-paths.js +24 -11
  71. package/dist/agents/schema/clean-for-gemini.js +132 -85
  72. package/dist/agents/session-slug.js +10 -5
  73. package/dist/agents/session-tool-result-guard-wrapper.js +1 -0
  74. package/dist/agents/session-tool-result-guard.js +3 -1
  75. package/dist/agents/session-transcript-repair.js +40 -6
  76. package/dist/agents/skills/bundled-dir.js +19 -5
  77. package/dist/agents/skills/env-overrides.js +124 -43
  78. package/dist/agents/skills/frontmatter.js +6 -6
  79. package/dist/agents/skills/plugin-skills.js +14 -7
  80. package/dist/agents/skills/workspace.js +1 -0
  81. package/dist/agents/skills.test-helpers.js +13 -0
  82. package/dist/agents/stable-stringify.js +12 -0
  83. package/dist/agents/subagent-announce.js +251 -49
  84. package/dist/agents/subagent-lifecycle-events.js +19 -0
  85. package/dist/agents/subagent-registry-cleanup.js +31 -0
  86. package/dist/agents/subagent-registry-completion.js +68 -0
  87. package/dist/agents/subagent-registry-queries.js +117 -0
  88. package/dist/agents/subagent-registry-state.js +46 -0
  89. package/dist/agents/subagent-registry.js +252 -221
  90. package/dist/agents/subagent-registry.mocks.shared.js +12 -0
  91. package/dist/agents/subagent-registry.store.js +1 -0
  92. package/dist/agents/subagent-registry.types.js +1 -0
  93. package/dist/agents/subagent-spawn.js +195 -7
  94. package/dist/agents/system-prompt.js +22 -6
  95. package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
  96. package/dist/agents/test-helpers/fast-coding-tools.js +1 -18
  97. package/dist/agents/test-helpers/fast-core-tools.js +1 -17
  98. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
  99. package/dist/agents/timeout.js +18 -6
  100. package/dist/agents/tool-call-id.js +1 -1
  101. package/dist/agents/tool-display-common.js +162 -29
  102. package/dist/agents/tool-images.js +82 -9
  103. package/dist/agents/tool-policy-shared.js +108 -0
  104. package/dist/agents/tool-policy.js +51 -26
  105. package/dist/agents/tools/browser-tool.js +160 -54
  106. package/dist/agents/tools/canvas-tool.js +27 -1
  107. package/dist/agents/tools/common.js +45 -0
  108. package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
  109. package/dist/agents/tools/discord-actions-guild.js +4 -1
  110. package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
  111. package/dist/agents/tools/gateway-tool.js +3 -1
  112. package/dist/agents/tools/image-tool.js +214 -99
  113. package/dist/agents/tools/nodes-utils.js +1 -10
  114. package/dist/agents/tools/sessions-history-tool.js +140 -108
  115. package/dist/agents/tools/sessions-send-helpers.js +12 -6
  116. package/dist/agents/tools/sessions-spawn-tool.js +8 -2
  117. package/dist/agents/tools/subagents-tool.js +2 -1
  118. package/dist/agents/tools/whatsapp-actions.js +10 -2
  119. package/dist/agents/tools/whatsapp-target-auth.js +18 -0
  120. package/dist/agents/transcript-policy.js +22 -8
  121. package/dist/agents/venice-models.js +11 -3
  122. package/dist/agents/workspace.js +222 -46
  123. package/dist/auto-reply/commands-registry.data.js +51 -0
  124. package/dist/auto-reply/commands-registry.js +19 -21
  125. package/dist/auto-reply/fallback-state.js +114 -0
  126. package/dist/auto-reply/group-activation.js +10 -5
  127. package/dist/auto-reply/inbound-debounce.js +10 -5
  128. package/dist/auto-reply/model-runtime.js +68 -0
  129. package/dist/auto-reply/reply/abort.js +1 -1
  130. package/dist/auto-reply/reply/agent-runner-execution.js +40 -5
  131. package/dist/auto-reply/reply/agent-runner.js +165 -39
  132. package/dist/auto-reply/reply/bash-command.js +41 -39
  133. package/dist/auto-reply/reply/command-gates.js +25 -0
  134. package/dist/auto-reply/reply/commands-allowlist.js +111 -72
  135. package/dist/auto-reply/reply/commands-bash.js +6 -5
  136. package/dist/auto-reply/reply/commands-config.js +30 -28
  137. package/dist/auto-reply/reply/commands-core.js +2 -1
  138. package/dist/auto-reply/reply/commands-info.js +1 -0
  139. package/dist/auto-reply/reply/commands-models.js +65 -14
  140. package/dist/auto-reply/reply/commands-session.js +237 -82
  141. package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
  142. package/dist/auto-reply/reply/commands-setunset.js +45 -0
  143. package/dist/auto-reply/reply/commands-subagents/action-agents.js +44 -0
  144. package/dist/auto-reply/reply/commands-subagents/action-focus.js +64 -0
  145. package/dist/auto-reply/reply/commands-subagents/action-help.js +4 -0
  146. package/dist/auto-reply/reply/commands-subagents/action-info.js +45 -0
  147. package/dist/auto-reply/reply/commands-subagents/action-kill.js +60 -0
  148. package/dist/auto-reply/reply/commands-subagents/action-list.js +44 -0
  149. package/dist/auto-reply/reply/commands-subagents/action-log.js +29 -0
  150. package/dist/auto-reply/reply/commands-subagents/action-send.js +119 -0
  151. package/dist/auto-reply/reply/commands-subagents/action-spawn.js +52 -0
  152. package/dist/auto-reply/reply/commands-subagents/action-unfocus.js +30 -0
  153. package/dist/auto-reply/reply/commands-subagents/shared.js +303 -0
  154. package/dist/auto-reply/reply/commands-subagents.js +51 -587
  155. package/dist/auto-reply/reply/commands-tts.js +10 -5
  156. package/dist/auto-reply/reply/config-value.js +10 -5
  157. package/dist/auto-reply/reply/directive-handling.model-picker.js +12 -6
  158. package/dist/auto-reply/reply/directive-handling.persist.js +9 -21
  159. package/dist/auto-reply/reply/directive-handling.shared.js +24 -4
  160. package/dist/auto-reply/reply/followup-runner.js +1 -0
  161. package/dist/auto-reply/reply/get-reply-directives-utils.js +23 -14
  162. package/dist/auto-reply/reply/get-reply-directives.js +17 -28
  163. package/dist/auto-reply/reply/get-reply-inline-actions.js +1 -0
  164. package/dist/auto-reply/reply/get-reply.js +71 -12
  165. package/dist/auto-reply/reply/model-selection.js +80 -39
  166. package/dist/auto-reply/reply/queue/enqueue.js +10 -5
  167. package/dist/auto-reply/reply/queue/state.js +13 -12
  168. package/dist/auto-reply/reply/reply-payloads.js +67 -36
  169. package/dist/auto-reply/reply/reply-reference.js +9 -8
  170. package/dist/auto-reply/reply/route-reply.js +15 -8
  171. package/dist/auto-reply/reply/session-reset-prompt.js +1 -1
  172. package/dist/auto-reply/reply/session.js +22 -6
  173. package/dist/auto-reply/reply/strip-inbound-meta.js +147 -0
  174. package/dist/auto-reply/reply/subagents-utils.js +56 -30
  175. package/dist/auto-reply/reply/typing.js +46 -21
  176. package/dist/auto-reply/send-policy.js +14 -7
  177. package/dist/auto-reply/status.js +140 -16
  178. package/dist/auto-reply/templating.js +10 -5
  179. package/dist/auto-reply/thinking.js +7 -16
  180. package/dist/auto-reply/tokens.js +21 -5
  181. package/dist/browser/bridge-server.js +36 -20
  182. package/dist/browser/cdp.helpers.js +7 -14
  183. package/dist/browser/cdp.js +35 -15
  184. package/dist/browser/chrome.profile-decoration.js +7 -4
  185. package/dist/browser/config.js +30 -0
  186. package/dist/browser/extension-relay-auth.js +55 -0
  187. package/dist/browser/extension-relay.js +74 -29
  188. package/dist/browser/navigation-guard.js +39 -0
  189. package/dist/browser/paths.js +77 -0
  190. package/dist/browser/profiles.js +13 -8
  191. package/dist/browser/pw-ai-module.js +10 -5
  192. package/dist/browser/pw-session.js +76 -39
  193. package/dist/browser/pw-tools-core.interactions.js +14 -7
  194. package/dist/browser/pw-tools-core.state.js +12 -6
  195. package/dist/browser/routes/agent.act.js +431 -424
  196. package/dist/browser/routes/agent.shared.js +47 -3
  197. package/dist/browser/routes/agent.snapshot.js +122 -116
  198. package/dist/browser/routes/agent.storage.js +303 -297
  199. package/dist/browser/routes/tabs.js +154 -100
  200. package/dist/browser/server-context.js +7 -0
  201. package/dist/browser/server-lifecycle.js +37 -0
  202. package/dist/build-info.json +3 -3
  203. package/dist/channels/allow-from.js +26 -0
  204. package/dist/channels/allowlists/resolve-utils.js +43 -19
  205. package/dist/channels/channel-config.js +14 -7
  206. package/dist/channels/draft-stream-loop.js +7 -0
  207. package/dist/channels/model-overrides.js +82 -0
  208. package/dist/channels/plugins/account-action-gate.js +13 -0
  209. package/dist/channels/plugins/message-actions.js +10 -0
  210. package/dist/channels/plugins/normalize/imessage.js +14 -7
  211. package/dist/channels/plugins/normalize/slack.js +10 -5
  212. package/dist/channels/plugins/normalize/telegram.js +14 -7
  213. package/dist/channels/plugins/outbound/discord.js +80 -8
  214. package/dist/channels/plugins/outbound/signal.js +11 -11
  215. package/dist/channels/plugins/setup-helpers.js +10 -5
  216. package/dist/channels/sender-label.js +14 -7
  217. package/dist/channels/session.js +4 -2
  218. package/dist/channels/status-reactions.js +297 -0
  219. package/dist/channels/telegram/api.js +18 -0
  220. package/dist/cli/argv.js +84 -21
  221. package/dist/cli/banner.js +3 -2
  222. package/dist/cli/browser-cli-actions-input/register.files-downloads.js +65 -56
  223. package/dist/cli/cli-name.js +11 -11
  224. package/dist/cli/cli-utils.js +13 -3
  225. package/dist/cli/command-format.js +1 -1
  226. package/dist/cli/config-cli.js +1 -1
  227. package/dist/cli/daemon-cli/lifecycle-core.js +31 -19
  228. package/dist/cli/daemon-cli/lifecycle.js +64 -2
  229. package/dist/cli/daemon-cli/restart-health.js +126 -0
  230. package/dist/cli/daemon-cli/status.gather.js +9 -13
  231. package/dist/cli/daemon-cli/status.print.js +2 -10
  232. package/dist/cli/deps.js +27 -22
  233. package/dist/cli/exec-approvals-cli.js +92 -124
  234. package/dist/cli/gateway-cli/run-loop.js +23 -5
  235. package/dist/cli/memory-cli.js +158 -61
  236. package/dist/cli/node-cli/register.js +14 -5
  237. package/dist/cli/nodes-cli/register.push.js +63 -0
  238. package/dist/cli/nodes-media-utils.js +26 -0
  239. package/dist/cli/outbound-send-deps.js +2 -9
  240. package/dist/cli/outbound-send-mapping.js +11 -0
  241. package/dist/cli/pairing-cli.js +40 -14
  242. package/dist/cli/plugins-cli.js +250 -73
  243. package/dist/cli/ports.js +11 -10
  244. package/dist/cli/program/build-program.js +3 -1
  245. package/dist/cli/program/command-registry.js +214 -136
  246. package/dist/cli/program/command-tree.js +16 -0
  247. package/dist/cli/program/help.js +43 -12
  248. package/dist/cli/program/preaction.js +13 -9
  249. package/dist/cli/program/register.configure.js +3 -18
  250. package/dist/cli/program/register.maintenance.js +2 -2
  251. package/dist/cli/program/register.onboard.js +2 -0
  252. package/dist/cli/program/register.status-health-sessions.js +16 -17
  253. package/dist/cli/program/register.subclis.js +93 -52
  254. package/dist/cli/route.js +12 -8
  255. package/dist/cli/system-cli.js +36 -46
  256. package/dist/cli/test-runtime-capture.js +24 -0
  257. package/dist/cli/update-cli/shared.js +22 -9
  258. package/dist/cli/update-cli/update-command.js +89 -14
  259. package/dist/cli/update-cli/wizard.js +6 -12
  260. package/dist/commands/agent/run-context.js +18 -5
  261. package/dist/commands/agent/session-store.js +17 -4
  262. package/dist/commands/agent.js +185 -89
  263. package/dist/commands/agents.bindings.js +14 -7
  264. package/dist/commands/agents.commands.add.js +13 -9
  265. package/dist/commands/agents.commands.identity.js +12 -6
  266. package/dist/commands/agents.commands.list.js +11 -6
  267. package/dist/commands/agents.config.js +8 -10
  268. package/dist/commands/agents.providers.js +12 -6
  269. package/dist/commands/auth-choice-options.js +103 -75
  270. package/dist/commands/auth-choice.apply.byteplus.js +55 -0
  271. package/dist/commands/auth-choice.apply.js +4 -0
  272. package/dist/commands/auth-choice.apply.minimax.js +61 -13
  273. package/dist/commands/auth-choice.apply.openai.js +3 -1
  274. package/dist/commands/auth-choice.apply.volcengine.js +55 -0
  275. package/dist/commands/auth-choice.preferred-provider.js +2 -0
  276. package/dist/commands/channels/remove.js +13 -6
  277. package/dist/commands/channels/shared.js +4 -14
  278. package/dist/commands/channels.mock-harness.js +23 -0
  279. package/dist/commands/configure.commands.js +14 -0
  280. package/dist/commands/configure.gateway.js +2 -4
  281. package/dist/commands/configure.js +1 -1
  282. package/dist/commands/configure.shared.js +11 -0
  283. package/dist/commands/daemon-install-helpers.js +2 -2
  284. package/dist/commands/daemon-install-runtime-warning.js +11 -0
  285. package/dist/commands/dashboard.js +12 -10
  286. package/dist/commands/docs.js +14 -8
  287. package/dist/commands/doctor-config-flow.js +11 -9
  288. package/dist/commands/doctor-legacy-config.js +281 -0
  289. package/dist/commands/doctor-state-integrity.js +99 -23
  290. package/dist/commands/doctor-update.js +12 -9
  291. package/dist/commands/models/list.list-command.js +7 -5
  292. package/dist/commands/models/set-image.js +2 -21
  293. package/dist/commands/node-daemon-install-helpers.js +10 -8
  294. package/dist/commands/onboard-auth.config-minimax.js +54 -80
  295. package/dist/commands/onboard-auth.config-opencode.js +2 -18
  296. package/dist/commands/onboard-auth.credentials.js +90 -13
  297. package/dist/commands/onboard-auth.js +1 -1
  298. package/dist/commands/onboard-auth.models.js +6 -5
  299. package/dist/commands/onboard-hooks.js +1 -1
  300. package/dist/commands/onboard-non-interactive/api-keys.js +14 -7
  301. package/dist/commands/onboard-non-interactive/local/auth-choice.js +64 -49
  302. package/dist/commands/onboard-provider-auth-flags.js +14 -0
  303. package/dist/commands/onboard-remote.js +14 -7
  304. package/dist/commands/onboard.js +11 -13
  305. package/dist/commands/sandbox-display.js +6 -5
  306. package/dist/commands/sessions.test-helpers.js +61 -0
  307. package/dist/commands/status-all/diagnosis.js +14 -10
  308. package/dist/commands/status-all/format.js +1 -0
  309. package/dist/commands/status.gateway-probe.js +1 -16
  310. package/dist/commands/systemd-linger.js +12 -6
  311. package/dist/config/agent-limits.js +2 -0
  312. package/dist/config/commands.js +32 -15
  313. package/dist/config/config-paths.js +9 -11
  314. package/dist/config/config.js +1 -1
  315. package/dist/config/defaults.js +22 -2
  316. package/dist/config/discord-preview-streaming.js +104 -0
  317. package/dist/config/env-substitution.js +62 -34
  318. package/dist/config/env-vars.js +45 -7
  319. package/dist/config/includes.js +4 -0
  320. package/dist/config/io.js +656 -171
  321. package/dist/config/legacy.migrations.part-1.js +189 -78
  322. package/dist/config/legacy.shared.js +3 -1
  323. package/dist/config/merge-patch.js +54 -4
  324. package/dist/config/prototype-keys.js +4 -0
  325. package/dist/config/redact-snapshot.js +404 -76
  326. package/dist/config/schema.help.js +44 -7
  327. package/dist/config/schema.js +58 -570
  328. package/dist/config/schema.labels.js +38 -6
  329. package/dist/config/sessions/delivery-info.js +10 -3
  330. package/dist/config/sessions/main-session.js +10 -5
  331. package/dist/config/sessions/session-file.js +33 -0
  332. package/dist/config/sessions/session-key.js +10 -5
  333. package/dist/config/sessions/store.js +1 -1
  334. package/dist/config/sessions.js +1 -0
  335. package/dist/config/validation.js +140 -85
  336. package/dist/config/zod-schema.agent-runtime.js +11 -0
  337. package/dist/config/zod-schema.hooks.js +40 -11
  338. package/dist/config/zod-schema.installs.js +20 -0
  339. package/dist/config/zod-schema.js +156 -20
  340. package/dist/config/zod-schema.providers-core.js +78 -4
  341. package/dist/config/zod-schema.providers.js +6 -1
  342. package/dist/config/zod-schema.session.js +41 -2
  343. package/dist/cron/run-log.js +3 -0
  344. package/dist/cron/schedule.js +21 -10
  345. package/dist/cron/service/ops.js +35 -21
  346. package/dist/cron/service/timer.js +116 -16
  347. package/dist/cron/stagger.js +3 -1
  348. package/dist/daemon/cmd-argv.js +21 -0
  349. package/dist/daemon/cmd-set.js +58 -0
  350. package/dist/daemon/service-types.js +1 -0
  351. package/dist/discord/api.js +12 -6
  352. package/dist/discord/draft-chunking.js +22 -0
  353. package/dist/discord/draft-stream.js +124 -0
  354. package/dist/discord/monitor/agent-components.js +1 -1
  355. package/dist/discord/monitor/commands.js +5 -0
  356. package/dist/discord/monitor/exec-approvals.js +357 -162
  357. package/dist/discord/monitor/gateway-plugin.js +2 -1
  358. package/dist/discord/monitor/listeners.js +37 -27
  359. package/dist/discord/monitor/message-handler.js +4 -1
  360. package/dist/discord/monitor/message-handler.preflight.js +65 -8
  361. package/dist/discord/monitor/message-handler.process.js +246 -217
  362. package/dist/discord/monitor/message-utils.js +143 -6
  363. package/dist/discord/monitor/model-picker-preferences.js +143 -0
  364. package/dist/discord/monitor/model-picker.js +651 -0
  365. package/dist/discord/monitor/native-command.js +573 -16
  366. package/dist/discord/monitor/provider.allowlist.js +223 -0
  367. package/dist/discord/monitor/provider.js +275 -347
  368. package/dist/discord/monitor/provider.lifecycle.js +100 -0
  369. package/dist/discord/monitor/reply-delivery.js +123 -16
  370. package/dist/discord/monitor/thread-bindings.discord-api.js +215 -0
  371. package/dist/discord/monitor/thread-bindings.js +4 -0
  372. package/dist/discord/monitor/thread-bindings.lifecycle.js +177 -0
  373. package/dist/discord/monitor/thread-bindings.manager.js +423 -0
  374. package/dist/discord/monitor/thread-bindings.messages.js +55 -0
  375. package/dist/discord/monitor/thread-bindings.state.js +358 -0
  376. package/dist/discord/monitor/thread-bindings.types.js +6 -0
  377. package/dist/discord/resolve-users.js +33 -21
  378. package/dist/discord/send.channels.js +15 -0
  379. package/dist/discord/send.js +3 -2
  380. package/dist/discord/send.outbound.js +82 -26
  381. package/dist/discord/send.permissions.js +83 -30
  382. package/dist/discord/send.reactions.js +8 -4
  383. package/dist/discord/token.js +10 -5
  384. package/dist/discord/voice/command.js +263 -0
  385. package/dist/discord/voice/manager.js +531 -0
  386. package/dist/gateway/auth.js +72 -13
  387. package/dist/gateway/call.js +152 -83
  388. package/dist/gateway/canvas-capability.js +75 -0
  389. package/dist/gateway/client.js +28 -4
  390. package/dist/gateway/config-reload.js +3 -4
  391. package/dist/gateway/control-plane-audit.js +28 -0
  392. package/dist/gateway/control-plane-rate-limit.js +53 -0
  393. package/dist/gateway/control-ui.js +219 -96
  394. package/dist/gateway/events.js +1 -0
  395. package/dist/gateway/hooks-mapping.js +88 -38
  396. package/dist/gateway/hooks.js +109 -54
  397. package/dist/gateway/http-auth-helpers.js +3 -2
  398. package/dist/gateway/http-common.js +22 -0
  399. package/dist/gateway/http-endpoint-helpers.js +1 -0
  400. package/dist/gateway/method-scopes.js +169 -0
  401. package/dist/gateway/net.js +74 -9
  402. package/dist/gateway/node-invoke-system-run-approval.js +14 -35
  403. package/dist/gateway/node-registry.js +10 -5
  404. package/dist/gateway/openai-http.js +1 -0
  405. package/dist/gateway/openresponses-http.js +121 -110
  406. package/dist/gateway/origin-check.js +1 -18
  407. package/dist/gateway/probe-auth.js +2 -0
  408. package/dist/gateway/protocol/index.js +4 -2
  409. package/dist/gateway/protocol/schema/cron.js +1 -0
  410. package/dist/gateway/protocol/schema/devices.js +1 -0
  411. package/dist/gateway/protocol/schema/protocol-schemas.js +4 -1
  412. package/dist/gateway/protocol/schema/push.js +18 -0
  413. package/dist/gateway/protocol/schema/sessions.js +6 -0
  414. package/dist/gateway/protocol/schema.js +1 -0
  415. package/dist/gateway/role-policy.js +17 -0
  416. package/dist/gateway/server/ws-connection/connect-policy.js +37 -0
  417. package/dist/gateway/server/ws-connection/message-handler.js +175 -148
  418. package/dist/gateway/server-chat.js +83 -25
  419. package/dist/gateway/server-constants.js +10 -9
  420. package/dist/gateway/server-cron.js +1 -0
  421. package/dist/gateway/server-http.js +247 -54
  422. package/dist/gateway/server-maintenance.js +20 -5
  423. package/dist/gateway/server-methods/agent.js +162 -24
  424. package/dist/gateway/server-methods/chat.js +465 -130
  425. package/dist/gateway/server-methods/config.js +193 -152
  426. package/dist/gateway/server-methods/devices.js +17 -3
  427. package/dist/gateway/server-methods/models.js +11 -1
  428. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  429. package/dist/gateway/server-methods/nodes.js +251 -69
  430. package/dist/gateway/server-methods/push.js +53 -0
  431. package/dist/gateway/server-methods/sessions.js +64 -8
  432. package/dist/gateway/server-methods/usage.js +162 -75
  433. package/dist/gateway/server-node-events.js +29 -0
  434. package/dist/gateway/server-reload-handlers.js +2 -3
  435. package/dist/gateway/server-runtime-config.js +39 -13
  436. package/dist/gateway/server-runtime-state.js +2 -0
  437. package/dist/gateway/server-startup-memory.js +17 -11
  438. package/dist/gateway/server-ws-runtime.js +1 -0
  439. package/dist/gateway/server.impl.js +296 -139
  440. package/dist/gateway/session-preview.test-helpers.js +11 -0
  441. package/dist/gateway/session-utils.fs.js +32 -34
  442. package/dist/gateway/sessions-resolve.js +17 -5
  443. package/dist/gateway/startup-auth.js +126 -0
  444. package/dist/gateway/test-helpers.agent-results.js +15 -0
  445. package/dist/gateway/test-helpers.mocks.js +37 -14
  446. package/dist/gateway/test-helpers.openai-mock.js +14 -7
  447. package/dist/gateway/test-helpers.server.js +161 -77
  448. package/dist/gateway/tools-invoke-http.js +21 -10
  449. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +3 -1
  450. package/dist/hooks/bundled/command-logger/handler.js +7 -2
  451. package/dist/hooks/bundled/session-memory/handler.js +170 -38
  452. package/dist/hooks/frontmatter.js +6 -6
  453. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  454. package/dist/hooks/gmail-watcher.js +11 -6
  455. package/dist/hooks/internal-hooks.js +11 -1
  456. package/dist/hooks/llm-slug-generator.js +4 -1
  457. package/dist/hooks/workspace.js +47 -17
  458. package/dist/imessage/accounts.js +9 -20
  459. package/dist/imessage/monitor/inbound-processing.js +2 -1
  460. package/dist/infra/archive-path.js +49 -0
  461. package/dist/infra/archive.js +174 -73
  462. package/dist/infra/control-ui-assets.js +14 -6
  463. package/dist/infra/device-pairing.js +204 -144
  464. package/dist/infra/env.js +10 -5
  465. package/dist/infra/exec-approvals-allowlist.js +141 -70
  466. package/dist/infra/exec-approvals-analysis.js +78 -20
  467. package/dist/infra/exec-approvals.js +5 -17
  468. package/dist/infra/exec-safe-bin-policy.js +277 -0
  469. package/dist/infra/fixed-window-rate-limit.js +33 -0
  470. package/dist/infra/fs-safe.js +71 -39
  471. package/dist/infra/gateway-lock.js +6 -2
  472. package/dist/infra/git-root.js +61 -0
  473. package/dist/infra/heartbeat-active-hours.js +2 -2
  474. package/dist/infra/heartbeat-reason.js +40 -0
  475. package/dist/infra/heartbeat-runner.js +72 -32
  476. package/dist/infra/heartbeat-wake.js +6 -12
  477. package/dist/infra/host-env-security-policy.json +19 -0
  478. package/dist/infra/host-env-security.js +66 -0
  479. package/dist/infra/install-source-utils.js +91 -7
  480. package/dist/infra/net/ssrf.js +131 -38
  481. package/dist/infra/node-pairing.js +50 -105
  482. package/dist/infra/npm-integrity.js +45 -0
  483. package/dist/infra/npm-pack-install.js +40 -0
  484. package/dist/infra/outbound/bound-delivery-router.js +88 -0
  485. package/dist/infra/outbound/channel-adapters.js +20 -7
  486. package/dist/infra/outbound/channel-selection.js +12 -6
  487. package/dist/infra/outbound/envelope.js +1 -1
  488. package/dist/infra/outbound/format.js +12 -6
  489. package/dist/infra/outbound/message-action-runner.js +107 -327
  490. package/dist/infra/outbound/message.js +59 -36
  491. package/dist/infra/outbound/outbound-policy.js +52 -25
  492. package/dist/infra/outbound/outbound-send-service.js +58 -71
  493. package/dist/infra/outbound/payloads.js +14 -7
  494. package/dist/infra/outbound/session-binding-service.js +123 -0
  495. package/dist/infra/pairing-files.js +10 -0
  496. package/dist/infra/path-guards.js +25 -0
  497. package/dist/infra/plain-object.js +9 -0
  498. package/dist/infra/provider-usage.fetch.codex.js +7 -15
  499. package/dist/infra/provider-usage.fetch.gemini.js +14 -11
  500. package/dist/infra/provider-usage.fetch.shared.js +30 -1
  501. package/dist/infra/provider-usage.fetch.zai.js +10 -9
  502. package/dist/infra/push-apns.js +365 -0
  503. package/dist/infra/restart-sentinel.js +16 -1
  504. package/dist/infra/restart.js +229 -26
  505. package/dist/infra/retry-policy.js +4 -2
  506. package/dist/infra/retry.js +9 -5
  507. package/dist/infra/scp-host.js +54 -0
  508. package/dist/infra/session-cost-usage.js +107 -59
  509. package/dist/infra/session-maintenance-warning.js +3 -1
  510. package/dist/infra/shell-env.js +98 -34
  511. package/dist/infra/ssh-config.js +12 -6
  512. package/dist/infra/system-run-command.js +49 -4
  513. package/dist/infra/update-channels.js +10 -5
  514. package/dist/infra/update-startup.js +86 -9
  515. package/dist/line/accounts.js +5 -7
  516. package/dist/line/bot-access.js +8 -20
  517. package/dist/line/bot-handlers.js +3 -1
  518. package/dist/link-understanding/detect.js +15 -7
  519. package/dist/media/constants.js +15 -6
  520. package/dist/media/image-ops.js +7 -0
  521. package/dist/media/inbound-path-policy.js +114 -0
  522. package/dist/media/input-files.js +16 -0
  523. package/dist/media/local-roots.js +3 -2
  524. package/dist/media-understanding/apply.js +4 -1
  525. package/dist/media-understanding/concurrency.js +8 -20
  526. package/dist/memory/backend-config.js +45 -6
  527. package/dist/memory/embeddings.js +10 -4
  528. package/dist/memory/fs-utils.js +23 -0
  529. package/dist/memory/manager-search.js +12 -6
  530. package/dist/memory/manager-sync-ops.js +12 -2
  531. package/dist/memory/qmd-manager.js +466 -53
  532. package/dist/memory/query-expansion.js +167 -3
  533. package/dist/memory/status-format.js +10 -5
  534. package/dist/memory/sync-memory-files.js +1 -1
  535. package/dist/memory/test-manager.js +8 -0
  536. package/dist/node-host/invoke-system-run.js +281 -0
  537. package/dist/node-host/invoke.js +55 -337
  538. package/dist/pairing/pairing-store.js +22 -0
  539. package/dist/plugin-sdk/allow-from.js +1 -1
  540. package/dist/plugin-sdk/command-auth.js +3 -1
  541. package/dist/plugin-sdk/index.js +6 -3
  542. package/dist/plugin-sdk/temp-path.js +47 -0
  543. package/dist/plugin-sdk/webhook-targets.js +32 -0
  544. package/dist/plugins/bundled-dir.js +9 -6
  545. package/dist/plugins/discovery.js +217 -23
  546. package/dist/plugins/hook-runner-global.js +16 -0
  547. package/dist/plugins/hooks.js +50 -0
  548. package/dist/plugins/install.js +28 -16
  549. package/dist/plugins/loader.js +192 -26
  550. package/dist/plugins/logger.js +8 -0
  551. package/dist/plugins/manifest-registry.js +3 -0
  552. package/dist/plugins/path-safety.js +34 -0
  553. package/dist/plugins/registry.js +5 -2
  554. package/dist/plugins/runtime/index.js +271 -206
  555. package/dist/plugins/runtime.js +3 -17
  556. package/dist/plugins/update.js +78 -12
  557. package/dist/process/spawn-utils.js +14 -7
  558. package/dist/providers/github-copilot-models.js +4 -1
  559. package/dist/providers/github-copilot-token.js +11 -6
  560. package/dist/providers/qwen-portal-oauth.js +14 -6
  561. package/dist/routing/account-id.js +30 -0
  562. package/dist/routing/resolve-route.js +3 -7
  563. package/dist/routing/session-key.js +2 -16
  564. package/dist/security/audit-channel.js +100 -20
  565. package/dist/security/audit-extra.async.js +505 -179
  566. package/dist/security/audit-extra.js +12 -2
  567. package/dist/security/audit-extra.sync.js +421 -35
  568. package/dist/security/audit-fs.js +31 -13
  569. package/dist/security/audit.js +180 -370
  570. package/dist/security/dm-policy-shared.js +68 -0
  571. package/dist/security/external-content.js +46 -14
  572. package/dist/security/fix.js +49 -85
  573. package/dist/security/scan-paths.js +20 -0
  574. package/dist/security/secret-equal.js +3 -7
  575. package/dist/security/windows-acl.js +30 -15
  576. package/dist/shared/entry-status.js +6 -0
  577. package/dist/shared/frontmatter.js +5 -5
  578. package/dist/shared/node-list-parse.js +13 -0
  579. package/dist/shared/node-match.js +11 -4
  580. package/dist/shared/operator-scope-compat.js +42 -0
  581. package/dist/shared/text-chunking.js +29 -0
  582. package/dist/signal/accounts.js +7 -20
  583. package/dist/signal/monitor/event-handler.js +3 -1
  584. package/dist/slack/accounts.js +6 -19
  585. package/dist/slack/actions.js +11 -3
  586. package/dist/slack/blocks.test-helpers.js +31 -0
  587. package/dist/slack/monitor/auth.js +1 -1
  588. package/dist/slack/monitor/message-handler/dispatch.js +50 -29
  589. package/dist/slack/monitor/mrkdwn.js +8 -0
  590. package/dist/slack/monitor/replies.js +15 -7
  591. package/dist/slack/monitor/slash.js +22 -13
  592. package/dist/slack/resolve-channels.js +10 -5
  593. package/dist/slack/send.js +102 -12
  594. package/dist/slack/stream-mode.js +10 -0
  595. package/dist/slack/streaming.js +4 -2
  596. package/dist/telegram/accounts.js +19 -14
  597. package/dist/telegram/bot/helpers.js +3 -5
  598. package/dist/telegram/bot-access.js +35 -36
  599. package/dist/telegram/bot-handlers.js +120 -148
  600. package/dist/telegram/bot-message-context.js +68 -9
  601. package/dist/telegram/bot-message-dispatch.js +477 -210
  602. package/dist/telegram/bot-native-commands.js +16 -0
  603. package/dist/telegram/draft-stream.js +44 -8
  604. package/dist/telegram/inline-buttons.js +5 -15
  605. package/dist/telegram/monitor.js +11 -7
  606. package/dist/telegram/network-config.js +19 -7
  607. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  608. package/dist/telegram/send.js +3 -2
  609. package/dist/telegram/sent-message-cache.js +5 -6
  610. package/dist/telegram/status-reaction-variants.js +208 -0
  611. package/dist/telegram/sticker-cache.js +11 -9
  612. package/dist/terminal/prompt-select-styled.js +9 -0
  613. package/dist/terminal/theme.js +12 -12
  614. package/dist/test-utils/command-runner.js +6 -0
  615. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  616. package/dist/test-utils/model-auth-mock.js +12 -0
  617. package/dist/test-utils/provider-usage-fetch.js +14 -0
  618. package/dist/test-utils/temp-home.js +33 -0
  619. package/dist/tts/tts.js +80 -567
  620. package/dist/tui/components/chat-log.js +50 -8
  621. package/dist/tui/theme/theme.js +10 -12
  622. package/dist/tui/tui-command-handlers.js +36 -27
  623. package/dist/tui/tui-event-handlers.js +122 -32
  624. package/dist/tui/tui-local-shell.js +16 -6
  625. package/dist/tui/tui.js +236 -48
  626. package/dist/utils/account-id.js +2 -4
  627. package/dist/utils/boolean.js +10 -5
  628. package/dist/utils/directive-tags.js +11 -0
  629. package/dist/utils/mask-api-key.js +10 -0
  630. package/dist/utils/queue-helpers.js +67 -12
  631. package/dist/utils/run-with-concurrency.js +39 -0
  632. package/dist/web/auto-reply/deliver-reply.js +8 -4
  633. package/dist/web/auto-reply/mentions.js +10 -5
  634. package/dist/web/auto-reply/monitor/group-members.js +14 -7
  635. package/dist/web/auto-reply/monitor/process-message.js +45 -24
  636. package/dist/web/inbound/access-control.js +5 -2
  637. package/dist/web/login-qr.js +12 -6
  638. package/dist/web/media.js +126 -15
  639. package/docs/tools/slash-commands.md +5 -1
  640. package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
  641. package/extensions/bluebubbles/src/monitor.ts +208 -1950
  642. package/extensions/feishu/src/external-keys.ts +19 -0
  643. package/extensions/lobster/src/windows-spawn.ts +193 -0
  644. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  645. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  646. package/package.json +1 -1
@@ -1,34 +1,224 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
- import { CURRENT_SESSION_VERSION } from "@mariozechner/pi-coding-agent";
3
+ import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
5
4
  import { resolveSessionAgentId } from "../../agents/agent-scope.js";
6
- import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../../agents/identity.js";
7
5
  import { resolveThinkingDefault } from "../../agents/model-selection.js";
8
6
  import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
9
7
  import { dispatchInboundMessage } from "../../auto-reply/dispatch.js";
10
8
  import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.js";
11
- import { extractShortModelName, } from "../../auto-reply/reply/response-prefix-template.js";
9
+ import { createReplyPrefixOptions } from "../../channels/reply-prefix.js";
10
+ import { resolveSessionFilePath } from "../../config/sessions.js";
12
11
  import { resolveSendPolicy } from "../../sessions/send-policy.js";
12
+ import { stripInlineDirectiveTagsForDisplay } from "../../utils/directive-tags.js";
13
13
  import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
14
14
  import { abortChatRunById, abortChatRunsForSessionKey, isChatStopCommandText, resolveChatRunExpiresAtMs, } from "../chat-abort.js";
15
15
  import { parseMessageWithAttachments } from "../chat-attachments.js";
16
+ import { stripEnvelopeFromMessages } from "../chat-sanitize.js";
17
+ import { GATEWAY_CLIENT_CAPS, hasGatewayClientCap } from "../protocol/client-info.js";
16
18
  import { ErrorCodes, errorShape, formatValidationErrors, validateChatAbortParams, validateChatHistoryParams, validateChatInjectParams, validateChatSendParams, } from "../protocol/index.js";
17
19
  import { getMaxChatHistoryMessagesBytes } from "../server-constants.js";
18
20
  import { capArrayByJsonBytes, loadSessionEntry, readSessionMessages, resolveSessionModelRef, } from "../session-utils.js";
19
- import { stripEnvelopeFromMessages } from "../chat-sanitize.js";
20
21
  import { formatForLog } from "../ws-log.js";
22
+ import { injectTimestamp, timestampOptsFromConfig } from "./agent-timestamp.js";
23
+ import { normalizeRpcAttachmentsToChatAttachments } from "./attachment-normalize.js";
24
+ const CHAT_HISTORY_TEXT_MAX_CHARS = 12_000;
25
+ const CHAT_HISTORY_MAX_SINGLE_MESSAGE_BYTES = 128 * 1024;
26
+ const CHAT_HISTORY_OVERSIZED_PLACEHOLDER = "[chat.history omitted: message too large]";
27
+ let chatHistoryPlaceholderEmitCount = 0;
28
+ function stripDisallowedChatControlChars(message) {
29
+ let output = "";
30
+ for (const char of message) {
31
+ const code = char.charCodeAt(0);
32
+ if (code === 9 || code === 10 || code === 13 || (code >= 32 && code !== 127)) {
33
+ output += char;
34
+ }
35
+ }
36
+ return output;
37
+ }
38
+ export function sanitizeChatSendMessageInput(message) {
39
+ const normalized = message.normalize("NFC");
40
+ if (normalized.includes("\u0000")) {
41
+ return { ok: false, error: "message must not contain null bytes" };
42
+ }
43
+ return { ok: true, message: stripDisallowedChatControlChars(normalized) };
44
+ }
45
+ function truncateChatHistoryText(text) {
46
+ if (text.length <= CHAT_HISTORY_TEXT_MAX_CHARS) {
47
+ return { text, truncated: false };
48
+ }
49
+ return {
50
+ text: `${text.slice(0, CHAT_HISTORY_TEXT_MAX_CHARS)}\n...(truncated)...`,
51
+ truncated: true,
52
+ };
53
+ }
54
+ function sanitizeChatHistoryContentBlock(block) {
55
+ if (!block || typeof block !== "object") {
56
+ return { block, changed: false };
57
+ }
58
+ const entry = { ...block };
59
+ let changed = false;
60
+ if (typeof entry.text === "string") {
61
+ const stripped = stripInlineDirectiveTagsForDisplay(entry.text);
62
+ const res = truncateChatHistoryText(stripped.text);
63
+ entry.text = res.text;
64
+ changed ||= stripped.changed || res.truncated;
65
+ }
66
+ if (typeof entry.partialJson === "string") {
67
+ const res = truncateChatHistoryText(entry.partialJson);
68
+ entry.partialJson = res.text;
69
+ changed ||= res.truncated;
70
+ }
71
+ if (typeof entry.arguments === "string") {
72
+ const res = truncateChatHistoryText(entry.arguments);
73
+ entry.arguments = res.text;
74
+ changed ||= res.truncated;
75
+ }
76
+ if (typeof entry.thinking === "string") {
77
+ const res = truncateChatHistoryText(entry.thinking);
78
+ entry.thinking = res.text;
79
+ changed ||= res.truncated;
80
+ }
81
+ if ("thinkingSignature" in entry) {
82
+ delete entry.thinkingSignature;
83
+ changed = true;
84
+ }
85
+ const type = typeof entry.type === "string" ? entry.type : "";
86
+ if (type === "image" && typeof entry.data === "string") {
87
+ const bytes = Buffer.byteLength(entry.data, "utf8");
88
+ delete entry.data;
89
+ entry.omitted = true;
90
+ entry.bytes = bytes;
91
+ changed = true;
92
+ }
93
+ return { block: changed ? entry : block, changed };
94
+ }
95
+ function sanitizeChatHistoryMessage(message) {
96
+ if (!message || typeof message !== "object") {
97
+ return { message, changed: false };
98
+ }
99
+ const entry = { ...message };
100
+ let changed = false;
101
+ if ("details" in entry) {
102
+ delete entry.details;
103
+ changed = true;
104
+ }
105
+ if ("usage" in entry) {
106
+ delete entry.usage;
107
+ changed = true;
108
+ }
109
+ if ("cost" in entry) {
110
+ delete entry.cost;
111
+ changed = true;
112
+ }
113
+ if (typeof entry.content === "string") {
114
+ const stripped = stripInlineDirectiveTagsForDisplay(entry.content);
115
+ const res = truncateChatHistoryText(stripped.text);
116
+ entry.content = res.text;
117
+ changed ||= stripped.changed || res.truncated;
118
+ }
119
+ else if (Array.isArray(entry.content)) {
120
+ const updated = entry.content.map((block) => sanitizeChatHistoryContentBlock(block));
121
+ if (updated.some((item) => item.changed)) {
122
+ entry.content = updated.map((item) => item.block);
123
+ changed = true;
124
+ }
125
+ }
126
+ if (typeof entry.text === "string") {
127
+ const stripped = stripInlineDirectiveTagsForDisplay(entry.text);
128
+ const res = truncateChatHistoryText(stripped.text);
129
+ entry.text = res.text;
130
+ changed ||= stripped.changed || res.truncated;
131
+ }
132
+ return { message: changed ? entry : message, changed };
133
+ }
134
+ function sanitizeChatHistoryMessages(messages) {
135
+ if (messages.length === 0) {
136
+ return messages;
137
+ }
138
+ let changed = false;
139
+ const next = messages.map((message) => {
140
+ const res = sanitizeChatHistoryMessage(message);
141
+ changed ||= res.changed;
142
+ return res.message;
143
+ });
144
+ return changed ? next : messages;
145
+ }
146
+ function jsonUtf8Bytes(value) {
147
+ try {
148
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
149
+ }
150
+ catch {
151
+ return Buffer.byteLength(String(value), "utf8");
152
+ }
153
+ }
154
+ function buildOversizedHistoryPlaceholder(message) {
155
+ const role = message &&
156
+ typeof message === "object" &&
157
+ typeof message.role === "string"
158
+ ? message.role
159
+ : "assistant";
160
+ const timestamp = message &&
161
+ typeof message === "object" &&
162
+ typeof message.timestamp === "number"
163
+ ? message.timestamp
164
+ : Date.now();
165
+ return {
166
+ role,
167
+ timestamp,
168
+ content: [{ type: "text", text: CHAT_HISTORY_OVERSIZED_PLACEHOLDER }],
169
+ __poolbot: { truncated: true, reason: "oversized" },
170
+ };
171
+ }
172
+ function replaceOversizedChatHistoryMessages(params) {
173
+ const { messages, maxSingleMessageBytes } = params;
174
+ if (messages.length === 0) {
175
+ return { messages, replacedCount: 0 };
176
+ }
177
+ let replacedCount = 0;
178
+ const next = messages.map((message) => {
179
+ if (jsonUtf8Bytes(message) <= maxSingleMessageBytes) {
180
+ return message;
181
+ }
182
+ replacedCount += 1;
183
+ return buildOversizedHistoryPlaceholder(message);
184
+ });
185
+ return { messages: replacedCount > 0 ? next : messages, replacedCount };
186
+ }
187
+ function enforceChatHistoryFinalBudget(params) {
188
+ const { messages, maxBytes } = params;
189
+ if (messages.length === 0) {
190
+ return { messages, placeholderCount: 0 };
191
+ }
192
+ if (jsonUtf8Bytes(messages) <= maxBytes) {
193
+ return { messages, placeholderCount: 0 };
194
+ }
195
+ const last = messages.at(-1);
196
+ if (last && jsonUtf8Bytes([last]) <= maxBytes) {
197
+ return { messages: [last], placeholderCount: 0 };
198
+ }
199
+ const placeholder = buildOversizedHistoryPlaceholder(last);
200
+ if (jsonUtf8Bytes([placeholder]) <= maxBytes) {
201
+ return { messages: [placeholder], placeholderCount: 1 };
202
+ }
203
+ return { messages: [], placeholderCount: 0 };
204
+ }
21
205
  function resolveTranscriptPath(params) {
22
- const { sessionId, storePath, sessionFile } = params;
23
- if (sessionFile)
24
- return sessionFile;
25
- if (!storePath)
206
+ const { sessionId, storePath, sessionFile, agentId } = params;
207
+ if (!storePath && !sessionFile) {
26
208
  return null;
27
- return path.join(path.dirname(storePath), `${sessionId}.jsonl`);
209
+ }
210
+ try {
211
+ const sessionsDir = storePath ? path.dirname(storePath) : undefined;
212
+ return resolveSessionFilePath(sessionId, sessionFile ? { sessionFile } : undefined, sessionsDir || agentId ? { sessionsDir, agentId } : undefined);
213
+ }
214
+ catch {
215
+ return null;
216
+ }
28
217
  }
29
218
  function ensureTranscriptFile(params) {
30
- if (fs.existsSync(params.transcriptPath))
219
+ if (fs.existsSync(params.transcriptPath)) {
31
220
  return { ok: true };
221
+ }
32
222
  try {
33
223
  fs.mkdirSync(path.dirname(params.transcriptPath), { recursive: true });
34
224
  const header = {
@@ -38,18 +228,40 @@ function ensureTranscriptFile(params) {
38
228
  timestamp: new Date().toISOString(),
39
229
  cwd: process.cwd(),
40
230
  };
41
- fs.writeFileSync(params.transcriptPath, `${JSON.stringify(header)}\n`, "utf-8");
231
+ fs.writeFileSync(params.transcriptPath, `${JSON.stringify(header)}\n`, {
232
+ encoding: "utf-8",
233
+ mode: 0o600,
234
+ });
42
235
  return { ok: true };
43
236
  }
44
237
  catch (err) {
45
238
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
46
239
  }
47
240
  }
241
+ function transcriptHasIdempotencyKey(transcriptPath, idempotencyKey) {
242
+ try {
243
+ const lines = fs.readFileSync(transcriptPath, "utf-8").split(/\r?\n/);
244
+ for (const line of lines) {
245
+ if (!line.trim()) {
246
+ continue;
247
+ }
248
+ const parsed = JSON.parse(line);
249
+ if (parsed?.message?.idempotencyKey === idempotencyKey) {
250
+ return true;
251
+ }
252
+ }
253
+ return false;
254
+ }
255
+ catch {
256
+ return false;
257
+ }
258
+ }
48
259
  function appendAssistantTranscriptMessage(params) {
49
260
  const transcriptPath = resolveTranscriptPath({
50
261
  sessionId: params.sessionId,
51
262
  storePath: params.storePath,
52
263
  sessionFile: params.sessionFile,
264
+ agentId: params.agentId,
53
265
  });
54
266
  if (!transcriptPath) {
55
267
  return { ok: false, error: "transcript path not resolved" };
@@ -66,29 +278,134 @@ function appendAssistantTranscriptMessage(params) {
66
278
  return { ok: false, error: ensured.error ?? "failed to create transcript file" };
67
279
  }
68
280
  }
281
+ if (params.idempotencyKey && transcriptHasIdempotencyKey(transcriptPath, params.idempotencyKey)) {
282
+ return { ok: true };
283
+ }
69
284
  const now = Date.now();
70
- const messageId = randomUUID().slice(0, 8);
71
285
  const labelPrefix = params.label ? `[${params.label}]\n\n` : "";
286
+ const usage = {
287
+ input: 0,
288
+ output: 0,
289
+ cacheRead: 0,
290
+ cacheWrite: 0,
291
+ totalTokens: 0,
292
+ cost: {
293
+ input: 0,
294
+ output: 0,
295
+ cacheRead: 0,
296
+ cacheWrite: 0,
297
+ total: 0,
298
+ },
299
+ };
72
300
  const messageBody = {
73
301
  role: "assistant",
74
302
  content: [{ type: "text", text: `${labelPrefix}${params.message}` }],
75
303
  timestamp: now,
76
- stopReason: "injected",
77
- usage: { input: 0, output: 0, totalTokens: 0 },
78
- };
79
- const transcriptEntry = {
80
- type: "message",
81
- id: messageId,
82
- timestamp: new Date(now).toISOString(),
83
- message: messageBody,
304
+ // Pi stopReason is a strict enum; this is not model output, but we still store it as a
305
+ // normal assistant message so it participates in the session parentId chain.
306
+ stopReason: "stop",
307
+ usage,
308
+ // Make these explicit so downstream tooling never treats this as model output.
309
+ api: "openai-responses",
310
+ provider: "poolbot",
311
+ model: "gateway-injected",
312
+ ...(params.idempotencyKey ? { idempotencyKey: params.idempotencyKey } : {}),
313
+ ...(params.abortMeta
314
+ ? {
315
+ poolbotAbort: {
316
+ aborted: true,
317
+ origin: params.abortMeta.origin,
318
+ runId: params.abortMeta.runId,
319
+ },
320
+ }
321
+ : {}),
84
322
  };
85
323
  try {
86
- fs.appendFileSync(transcriptPath, `${JSON.stringify(transcriptEntry)}\n`, "utf-8");
324
+ // IMPORTANT: Use SessionManager so the entry is attached to the current leaf via parentId.
325
+ // Raw jsonl appends break the parent chain and can hide compaction summaries from context.
326
+ const sessionManager = SessionManager.open(transcriptPath);
327
+ const messageId = sessionManager.appendMessage(messageBody);
328
+ return { ok: true, messageId, message: messageBody };
87
329
  }
88
330
  catch (err) {
89
331
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
90
332
  }
91
- return { ok: true, messageId, message: transcriptEntry.message };
333
+ }
334
+ function collectSessionAbortPartials(params) {
335
+ const out = [];
336
+ for (const [runId, active] of params.chatAbortControllers) {
337
+ if (active.sessionKey !== params.sessionKey) {
338
+ continue;
339
+ }
340
+ const text = params.chatRunBuffers.get(runId);
341
+ if (!text || !text.trim()) {
342
+ continue;
343
+ }
344
+ out.push({
345
+ runId,
346
+ sessionId: active.sessionId,
347
+ text,
348
+ abortOrigin: params.abortOrigin,
349
+ });
350
+ }
351
+ return out;
352
+ }
353
+ function persistAbortedPartials(params) {
354
+ if (params.snapshots.length === 0) {
355
+ return;
356
+ }
357
+ const { storePath, entry } = loadSessionEntry(params.sessionKey);
358
+ for (const snapshot of params.snapshots) {
359
+ const sessionId = entry?.sessionId ?? snapshot.sessionId ?? snapshot.runId;
360
+ const appended = appendAssistantTranscriptMessage({
361
+ message: snapshot.text,
362
+ sessionId,
363
+ storePath,
364
+ sessionFile: entry?.sessionFile,
365
+ createIfMissing: true,
366
+ idempotencyKey: `${snapshot.runId}:assistant`,
367
+ abortMeta: {
368
+ aborted: true,
369
+ origin: snapshot.abortOrigin,
370
+ runId: snapshot.runId,
371
+ },
372
+ });
373
+ if (!appended.ok) {
374
+ params.context.logGateway.warn(`chat.abort transcript append failed: ${appended.error ?? "unknown error"}`);
375
+ }
376
+ }
377
+ }
378
+ function createChatAbortOps(context) {
379
+ return {
380
+ chatAbortControllers: context.chatAbortControllers,
381
+ chatRunBuffers: context.chatRunBuffers,
382
+ chatDeltaSentAt: context.chatDeltaSentAt,
383
+ chatAbortedRuns: context.chatAbortedRuns,
384
+ removeChatRun: context.removeChatRun,
385
+ agentRunSeq: context.agentRunSeq,
386
+ broadcast: context.broadcast,
387
+ nodeSendToSession: context.nodeSendToSession,
388
+ };
389
+ }
390
+ function abortChatRunsForSessionKeyWithPartials(params) {
391
+ const snapshots = collectSessionAbortPartials({
392
+ chatAbortControllers: params.context.chatAbortControllers,
393
+ chatRunBuffers: params.context.chatRunBuffers,
394
+ sessionKey: params.sessionKey,
395
+ abortOrigin: params.abortOrigin,
396
+ });
397
+ const res = abortChatRunsForSessionKey(params.ops, {
398
+ sessionKey: params.sessionKey,
399
+ stopReason: params.stopReason,
400
+ });
401
+ if (res.aborted) {
402
+ persistAbortedPartials({
403
+ context: params.context,
404
+ sessionKey: params.sessionKey,
405
+ snapshots,
406
+ });
407
+ }
408
+ return res;
92
409
  }
93
410
  function nextChatSeq(context, runId) {
94
411
  const next = (context.agentRunSeq.get(runId) ?? 0) + 1;
@@ -106,6 +423,7 @@ function broadcastChatFinal(params) {
106
423
  };
107
424
  params.context.broadcast("chat", payload);
108
425
  params.context.nodeSendToSession(params.sessionKey, "chat", payload);
426
+ params.context.agentRunSeq.delete(params.runId);
109
427
  }
110
428
  function broadcastChatError(params) {
111
429
  const seq = nextChatSeq({ agentRunSeq: params.context.agentRunSeq }, params.runId);
@@ -118,6 +436,7 @@ function broadcastChatError(params) {
118
436
  };
119
437
  params.context.broadcast("chat", payload);
120
438
  params.context.nodeSendToSession(params.sessionKey, "chat", payload);
439
+ params.context.agentRunSeq.delete(params.runId);
121
440
  }
122
441
  export const chatHandlers = {
123
442
  "chat.history": async ({ params, respond, context }) => {
@@ -135,7 +454,20 @@ export const chatHandlers = {
135
454
  const max = Math.min(hardMax, requested);
136
455
  const sliced = rawMessages.length > max ? rawMessages.slice(-max) : rawMessages;
137
456
  const sanitized = stripEnvelopeFromMessages(sliced);
138
- const capped = capArrayByJsonBytes(sanitized, getMaxChatHistoryMessagesBytes()).items;
457
+ const normalized = sanitizeChatHistoryMessages(sanitized);
458
+ const maxHistoryBytes = getMaxChatHistoryMessagesBytes();
459
+ const perMessageHardCap = Math.min(CHAT_HISTORY_MAX_SINGLE_MESSAGE_BYTES, maxHistoryBytes);
460
+ const replaced = replaceOversizedChatHistoryMessages({
461
+ messages: normalized,
462
+ maxSingleMessageBytes: perMessageHardCap,
463
+ });
464
+ const capped = capArrayByJsonBytes(replaced.messages, maxHistoryBytes).items;
465
+ const bounded = enforceChatHistoryFinalBudget({ messages: capped, maxBytes: maxHistoryBytes });
466
+ const placeholderCount = replaced.replacedCount + bounded.placeholderCount;
467
+ if (placeholderCount > 0) {
468
+ chatHistoryPlaceholderEmitCount += placeholderCount;
469
+ context.logGateway.debug(`chat.history omitted oversized payloads placeholders=${placeholderCount} total=${chatHistoryPlaceholderEmitCount}`);
470
+ }
139
471
  let thinkingLevel = entry?.thinkingLevel;
140
472
  if (!thinkingLevel) {
141
473
  const configured = cfg.agents?.defaults?.thinkingDefault;
@@ -143,7 +475,8 @@ export const chatHandlers = {
143
475
  thinkingLevel = configured;
144
476
  }
145
477
  else {
146
- const { provider, model } = resolveSessionModelRef(cfg, entry);
478
+ const sessionAgentId = resolveSessionAgentId({ sessionKey, config: cfg });
479
+ const { provider, model } = resolveSessionModelRef(cfg, entry, sessionAgentId);
147
480
  const catalog = await context.loadGatewayModelCatalog();
148
481
  thinkingLevel = resolveThinkingDefault({
149
482
  cfg,
@@ -153,11 +486,13 @@ export const chatHandlers = {
153
486
  });
154
487
  }
155
488
  }
489
+ const verboseLevel = entry?.verboseLevel ?? cfg.agents?.defaults?.verboseDefault;
156
490
  respond(true, {
157
491
  sessionKey,
158
492
  sessionId,
159
- messages: capped,
493
+ messages: bounded.messages,
160
494
  thinkingLevel,
495
+ verboseLevel,
161
496
  });
162
497
  },
163
498
  "chat.abort": ({ params, respond, context }) => {
@@ -165,20 +500,14 @@ export const chatHandlers = {
165
500
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid chat.abort params: ${formatValidationErrors(validateChatAbortParams.errors)}`));
166
501
  return;
167
502
  }
168
- const { sessionKey, runId } = params;
169
- const ops = {
170
- chatAbortControllers: context.chatAbortControllers,
171
- chatRunBuffers: context.chatRunBuffers,
172
- chatDeltaSentAt: context.chatDeltaSentAt,
173
- chatAbortedRuns: context.chatAbortedRuns,
174
- removeChatRun: context.removeChatRun,
175
- agentRunSeq: context.agentRunSeq,
176
- broadcast: context.broadcast,
177
- nodeSendToSession: context.nodeSendToSession,
178
- };
503
+ const { sessionKey: rawSessionKey, runId } = params;
504
+ const ops = createChatAbortOps(context);
179
505
  if (!runId) {
180
- const res = abortChatRunsForSessionKey(ops, {
181
- sessionKey,
506
+ const res = abortChatRunsForSessionKeyWithPartials({
507
+ context,
508
+ ops,
509
+ sessionKey: rawSessionKey,
510
+ abortOrigin: "rpc",
182
511
  stopReason: "rpc",
183
512
  });
184
513
  respond(true, { ok: true, aborted: res.aborted, runIds: res.runIds });
@@ -189,15 +518,30 @@ export const chatHandlers = {
189
518
  respond(true, { ok: true, aborted: false, runIds: [] });
190
519
  return;
191
520
  }
192
- if (active.sessionKey !== sessionKey) {
521
+ if (active.sessionKey !== rawSessionKey) {
193
522
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "runId does not match sessionKey"));
194
523
  return;
195
524
  }
525
+ const partialText = context.chatRunBuffers.get(runId);
196
526
  const res = abortChatRunById(ops, {
197
527
  runId,
198
- sessionKey,
528
+ sessionKey: rawSessionKey,
199
529
  stopReason: "rpc",
200
530
  });
531
+ if (res.aborted && partialText && partialText.trim()) {
532
+ persistAbortedPartials({
533
+ context,
534
+ sessionKey: rawSessionKey,
535
+ snapshots: [
536
+ {
537
+ runId,
538
+ sessionId: active.sessionId,
539
+ text: partialText,
540
+ abortOrigin: "rpc",
541
+ },
542
+ ],
543
+ });
544
+ }
201
545
  respond(true, {
202
546
  ok: true,
203
547
  aborted: res.aborted,
@@ -210,29 +554,24 @@ export const chatHandlers = {
210
554
  return;
211
555
  }
212
556
  const p = params;
213
- const stopCommand = isChatStopCommandText(p.message);
214
- const normalizedAttachments = p.attachments
215
- ?.map((a) => ({
216
- type: typeof a?.type === "string" ? a.type : undefined,
217
- mimeType: typeof a?.mimeType === "string" ? a.mimeType : undefined,
218
- fileName: typeof a?.fileName === "string" ? a.fileName : undefined,
219
- content: typeof a?.content === "string"
220
- ? a.content
221
- : ArrayBuffer.isView(a?.content)
222
- ? Buffer.from(a.content.buffer, a.content.byteOffset, a.content.byteLength).toString("base64")
223
- : undefined,
224
- }))
225
- .filter((a) => a.content) ?? [];
226
- const rawMessage = p.message.trim();
557
+ const sanitizedMessageResult = sanitizeChatSendMessageInput(p.message);
558
+ if (!sanitizedMessageResult.ok) {
559
+ respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, sanitizedMessageResult.error));
560
+ return;
561
+ }
562
+ const inboundMessage = sanitizedMessageResult.message;
563
+ const stopCommand = isChatStopCommandText(inboundMessage);
564
+ const normalizedAttachments = normalizeRpcAttachmentsToChatAttachments(p.attachments);
565
+ const rawMessage = inboundMessage.trim();
227
566
  if (!rawMessage && normalizedAttachments.length === 0) {
228
567
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "message or attachment required"));
229
568
  return;
230
569
  }
231
- let parsedMessage = p.message;
570
+ let parsedMessage = inboundMessage;
232
571
  let parsedImages = [];
233
572
  if (normalizedAttachments.length > 0) {
234
573
  try {
235
- const parsed = await parseMessageWithAttachments(p.message, normalizedAttachments, {
574
+ const parsed = await parseMessageWithAttachments(inboundMessage, normalizedAttachments, {
236
575
  maxBytes: 5_000_000,
237
576
  log: context.logGateway,
238
577
  });
@@ -244,7 +583,8 @@ export const chatHandlers = {
244
583
  return;
245
584
  }
246
585
  }
247
- const { cfg, entry } = loadSessionEntry(p.sessionKey);
586
+ const rawSessionKey = p.sessionKey;
587
+ const { cfg, entry, canonicalKey: sessionKey } = loadSessionEntry(rawSessionKey);
248
588
  const timeoutMs = resolveAgentTimeoutMs({
249
589
  cfg,
250
590
  overrideMs: p.timeoutMs,
@@ -254,7 +594,7 @@ export const chatHandlers = {
254
594
  const sendPolicy = resolveSendPolicy({
255
595
  cfg,
256
596
  entry,
257
- sessionKey: p.sessionKey,
597
+ sessionKey,
258
598
  channel: entry?.channel,
259
599
  chatType: entry?.chatType,
260
600
  });
@@ -263,16 +603,13 @@ export const chatHandlers = {
263
603
  return;
264
604
  }
265
605
  if (stopCommand) {
266
- const res = abortChatRunsForSessionKey({
267
- chatAbortControllers: context.chatAbortControllers,
268
- chatRunBuffers: context.chatRunBuffers,
269
- chatDeltaSentAt: context.chatDeltaSentAt,
270
- chatAbortedRuns: context.chatAbortedRuns,
271
- removeChatRun: context.removeChatRun,
272
- agentRunSeq: context.agentRunSeq,
273
- broadcast: context.broadcast,
274
- nodeSendToSession: context.nodeSendToSession,
275
- }, { sessionKey: p.sessionKey, stopReason: "stop" });
606
+ const res = abortChatRunsForSessionKeyWithPartials({
607
+ context,
608
+ ops: createChatAbortOps(context),
609
+ sessionKey: rawSessionKey,
610
+ abortOrigin: "stop-command",
611
+ stopReason: "stop",
612
+ });
276
613
  respond(true, { ok: true, aborted: res.aborted, runIds: res.runIds });
277
614
  return;
278
615
  }
@@ -296,7 +633,7 @@ export const chatHandlers = {
296
633
  context.chatAbortControllers.set(clientRunId, {
297
634
  controller: abortController,
298
635
  sessionId: entry?.sessionId ?? clientRunId,
299
- sessionKey: p.sessionKey,
636
+ sessionKey: rawSessionKey,
300
637
  startedAtMs: now,
301
638
  expiresAtMs: resolveChatRunExpiresAtMs({ now, timeoutMs }),
302
639
  });
@@ -309,13 +646,17 @@ export const chatHandlers = {
309
646
  const injectThinking = Boolean(p.thinking && trimmedMessage && !trimmedMessage.startsWith("/"));
310
647
  const commandBody = injectThinking ? `/think ${p.thinking} ${parsedMessage}` : parsedMessage;
311
648
  const clientInfo = client?.connect?.client;
649
+ // Inject timestamp so agents know the current date/time.
650
+ // Only BodyForAgent gets the timestamp — Body stays raw for UI display.
651
+ // See: https://github.com/moltbot/moltbot/issues/3658
652
+ const stampedMessage = injectTimestamp(parsedMessage, timestampOptsFromConfig(cfg));
312
653
  const ctx = {
313
654
  Body: parsedMessage,
314
- BodyForAgent: parsedMessage,
655
+ BodyForAgent: stampedMessage,
315
656
  BodyForCommands: commandBody,
316
657
  RawBody: parsedMessage,
317
658
  CommandBody: commandBody,
318
- SessionKey: p.sessionKey,
659
+ SessionKey: sessionKey,
319
660
  Provider: INTERNAL_MESSAGE_CHANNEL,
320
661
  Surface: INTERNAL_MESSAGE_CHANNEL,
321
662
  OriginatingChannel: INTERNAL_MESSAGE_CHANNEL,
@@ -325,27 +666,31 @@ export const chatHandlers = {
325
666
  SenderId: clientInfo?.id,
326
667
  SenderName: clientInfo?.displayName,
327
668
  SenderUsername: clientInfo?.displayName,
669
+ GatewayClientScopes: client?.connect?.scopes,
328
670
  };
329
671
  const agentId = resolveSessionAgentId({
330
- sessionKey: p.sessionKey,
672
+ sessionKey,
331
673
  config: cfg,
332
674
  });
333
- let prefixContext = {
334
- identityName: resolveIdentityName(cfg, agentId),
335
- };
675
+ const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
676
+ cfg,
677
+ agentId,
678
+ channel: INTERNAL_MESSAGE_CHANNEL,
679
+ });
336
680
  const finalReplyParts = [];
337
681
  const dispatcher = createReplyDispatcher({
338
- responsePrefix: resolveEffectiveMessagesConfig(cfg, agentId).responsePrefix,
339
- responsePrefixContextProvider: () => prefixContext,
682
+ ...prefixOptions,
340
683
  onError: (err) => {
341
684
  context.logGateway.warn(`webchat dispatch failed: ${formatForLog(err)}`);
342
685
  },
343
686
  deliver: async (payload, info) => {
344
- if (info.kind !== "final")
687
+ if (info.kind !== "final") {
345
688
  return;
689
+ }
346
690
  const text = payload.text?.trim() ?? "";
347
- if (!text)
691
+ if (!text) {
348
692
  return;
693
+ }
349
694
  finalReplyParts.push(text);
350
695
  },
351
696
  });
@@ -358,16 +703,23 @@ export const chatHandlers = {
358
703
  runId: clientRunId,
359
704
  abortSignal: abortController.signal,
360
705
  images: parsedImages.length > 0 ? parsedImages : undefined,
361
- disableBlockStreaming: true,
362
- onAgentRunStart: () => {
706
+ onAgentRunStart: (runId) => {
363
707
  agentRunStarted = true;
708
+ const connId = typeof client?.connId === "string" ? client.connId : undefined;
709
+ const wantsToolEvents = hasGatewayClientCap(client?.connect?.caps, GATEWAY_CLIENT_CAPS.TOOL_EVENTS);
710
+ if (connId && wantsToolEvents) {
711
+ context.registerToolEventRecipient(runId, connId);
712
+ // Register for any other active runs *in the same session* so
713
+ // late-joining clients (e.g. page refresh mid-response) receive
714
+ // in-progress tool events without leaking cross-session data.
715
+ for (const [activeRunId, active] of context.chatAbortControllers) {
716
+ if (activeRunId !== runId && active.sessionKey === p.sessionKey) {
717
+ context.registerToolEventRecipient(activeRunId, connId);
718
+ }
719
+ }
720
+ }
364
721
  },
365
- onModelSelected: (ctx) => {
366
- prefixContext.provider = ctx.provider;
367
- prefixContext.model = extractShortModelName(ctx.model);
368
- prefixContext.modelFull = `${ctx.provider}/${ctx.model}`;
369
- prefixContext.thinkingLevel = ctx.thinkLevel ?? "off";
370
- },
722
+ onModelSelected,
371
723
  },
372
724
  })
373
725
  .then(() => {
@@ -379,13 +731,14 @@ export const chatHandlers = {
379
731
  .trim();
380
732
  let message;
381
733
  if (combinedReply) {
382
- const { storePath: latestStorePath, entry: latestEntry } = loadSessionEntry(p.sessionKey);
734
+ const { storePath: latestStorePath, entry: latestEntry } = loadSessionEntry(sessionKey);
383
735
  const sessionId = latestEntry?.sessionId ?? entry?.sessionId ?? clientRunId;
384
736
  const appended = appendAssistantTranscriptMessage({
385
737
  message: combinedReply,
386
738
  sessionId,
387
739
  storePath: latestStorePath,
388
740
  sessionFile: latestEntry?.sessionFile,
741
+ agentId,
389
742
  createIfMissing: true,
390
743
  });
391
744
  if (appended.ok) {
@@ -398,7 +751,9 @@ export const chatHandlers = {
398
751
  role: "assistant",
399
752
  content: [{ type: "text", text: combinedReply }],
400
753
  timestamp: now,
401
- stopReason: "injected",
754
+ // Keep this compatible with Pi stopReason enums even though this message isn't
755
+ // persisted to the transcript due to the append failure.
756
+ stopReason: "stop",
402
757
  usage: { input: 0, output: 0, totalTokens: 0 },
403
758
  };
404
759
  }
@@ -406,7 +761,7 @@ export const chatHandlers = {
406
761
  broadcastChatFinal({
407
762
  context,
408
763
  runId: clientRunId,
409
- sessionKey: p.sessionKey,
764
+ sessionKey: rawSessionKey,
410
765
  message,
411
766
  });
412
767
  }
@@ -431,7 +786,7 @@ export const chatHandlers = {
431
786
  broadcastChatError({
432
787
  context,
433
788
  runId: clientRunId,
434
- sessionKey: p.sessionKey,
789
+ sessionKey: rawSessionKey,
435
790
  errorMessage: String(err),
436
791
  });
437
792
  })
@@ -465,56 +820,36 @@ export const chatHandlers = {
465
820
  }
466
821
  const p = params;
467
822
  // Load session to find transcript file
468
- const { storePath, entry } = loadSessionEntry(p.sessionKey);
823
+ const rawSessionKey = p.sessionKey;
824
+ const { cfg, storePath, entry } = loadSessionEntry(rawSessionKey);
469
825
  const sessionId = entry?.sessionId;
470
826
  if (!sessionId || !storePath) {
471
827
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "session not found"));
472
828
  return;
473
829
  }
474
- // Resolve transcript path
475
- const transcriptPath = entry?.sessionFile
476
- ? entry.sessionFile
477
- : path.join(path.dirname(storePath), `${sessionId}.jsonl`);
478
- if (!fs.existsSync(transcriptPath)) {
479
- respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "transcript file not found"));
480
- return;
481
- }
482
- // Build transcript entry
483
- const now = Date.now();
484
- const messageId = randomUUID().slice(0, 8);
485
- const labelPrefix = p.label ? `[${p.label}]\n\n` : "";
486
- const messageBody = {
487
- role: "assistant",
488
- content: [{ type: "text", text: `${labelPrefix}${p.message}` }],
489
- timestamp: now,
490
- stopReason: "injected",
491
- usage: { input: 0, output: 0, totalTokens: 0 },
492
- };
493
- const transcriptEntry = {
494
- type: "message",
495
- id: messageId,
496
- timestamp: new Date(now).toISOString(),
497
- message: messageBody,
498
- };
499
- // Append to transcript file
500
- try {
501
- fs.appendFileSync(transcriptPath, `${JSON.stringify(transcriptEntry)}\n`, "utf-8");
502
- }
503
- catch (err) {
504
- const errMessage = err instanceof Error ? err.message : String(err);
505
- respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, `failed to write transcript: ${errMessage}`));
830
+ const appended = appendAssistantTranscriptMessage({
831
+ message: p.message,
832
+ label: p.label,
833
+ sessionId,
834
+ storePath,
835
+ sessionFile: entry?.sessionFile,
836
+ agentId: resolveSessionAgentId({ sessionKey: rawSessionKey, config: cfg }),
837
+ createIfMissing: false,
838
+ });
839
+ if (!appended.ok || !appended.messageId || !appended.message) {
840
+ respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, `failed to write transcript: ${appended.error ?? "unknown error"}`));
506
841
  return;
507
842
  }
508
843
  // Broadcast to webchat for immediate UI update
509
844
  const chatPayload = {
510
- runId: `inject-${messageId}`,
511
- sessionKey: p.sessionKey,
845
+ runId: `inject-${appended.messageId}`,
846
+ sessionKey: rawSessionKey,
512
847
  seq: 0,
513
848
  state: "final",
514
- message: transcriptEntry.message,
849
+ message: appended.message,
515
850
  };
516
851
  context.broadcast("chat", chatPayload);
517
- context.nodeSendToSession(p.sessionKey, "chat", chatPayload);
518
- respond(true, { ok: true, messageId });
852
+ context.nodeSendToSession(rawSessionKey, "chat", chatPayload);
853
+ respond(true, { ok: true, messageId: appended.messageId });
519
854
  },
520
855
  };