mixdog 1.3.4 → 1.4.0

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 (472) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +17 -2
  3. package/README.md +11 -0
  4. package/package.json +3 -11
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/embed-google-oauth-client.mjs +49 -0
  7. package/scripts/gate-local.mjs +4 -2
  8. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  9. package/scripts/native-release-status.mjs +2 -2
  10. package/scripts/recall-history-cases.json +1 -1
  11. package/scripts/suite-health.mjs +10 -1
  12. package/scripts/test.mjs +12 -11
  13. package/src/agents/reviewer/AGENT.md +7 -11
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/defaults/shipped-history.json +25 -1
  16. package/src/defaults/skills/browser-use/SKILL.md +77 -17
  17. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  18. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  19. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  20. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  21. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  22. package/src/defaults/skills/setup/SKILL.md +29 -2
  23. package/src/defaults/skills/setup/references/actions.md +117 -28
  24. package/src/defaults/skills/setup/references/surfaces.md +77 -35
  25. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  26. package/src/defaults/skills/visualize/SKILL.md +2 -1
  27. package/src/lib/keychain-cjs.cjs +359 -29
  28. package/src/lib/rules-builder.cjs +10 -8
  29. package/src/rules/agent/AGENT.md +6 -6
  30. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  31. package/src/rules/agent-mode/MIXY.md +12 -0
  32. package/src/rules/lead/LEAD.md +18 -20
  33. package/src/rules/shared/05-tool-calls.md +5 -6
  34. package/src/rules/shared/10-tool-workflow.md +2 -0
  35. package/src/rules/shared/60-verification.md +7 -5
  36. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  37. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  38. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/config.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  42. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  43. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  44. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  45. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
  46. package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  49. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  50. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  51. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  53. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
  54. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
  55. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  56. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  60. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
  61. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  65. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  66. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  67. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  68. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  69. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
  70. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  78. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  79. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  81. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  82. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  83. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  84. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  85. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  86. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  87. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  89. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  90. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  91. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  92. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  93. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  94. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  95. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  97. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  98. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  99. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  100. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  101. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  113. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  114. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  115. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  116. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  117. package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
  118. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  119. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  120. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  121. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  122. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  127. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  128. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  131. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  132. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  133. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  134. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  135. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  136. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  137. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  138. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  139. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  140. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  141. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
  142. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  143. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  144. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
  145. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
  146. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
  147. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  148. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
  149. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  151. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  152. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
  153. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  154. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
  155. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  156. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
  157. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  158. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
  159. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
  164. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  165. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  166. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  172. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  173. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  174. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  175. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  176. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  177. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  178. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  179. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  180. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  182. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  183. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  184. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  185. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  186. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  187. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  188. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  189. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  191. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  192. package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
  193. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  203. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  207. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  208. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  209. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  214. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
  216. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  217. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  218. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  219. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  225. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  226. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  227. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  228. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  229. package/src/runtime/browser-bridge/client.mjs +73 -8
  230. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  231. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  232. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  233. package/src/runtime/channels/lib/config.mjs +6 -61
  234. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  235. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  236. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  237. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  238. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  239. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  240. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  241. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  242. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  243. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  244. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  245. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  246. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  247. package/src/runtime/channels/lib/scheduler.mjs +135 -154
  248. package/src/runtime/channels/lib/schedules-db.mjs +83 -11
  249. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  250. package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
  251. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  252. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  253. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  254. package/src/runtime/channels/lib/worker-main.mjs +19 -34
  255. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  256. package/src/runtime/computer-bridge/actions.mjs +9 -1
  257. package/src/runtime/computer-bridge/client.mjs +180 -8
  258. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  259. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  260. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  261. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  262. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  263. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  264. package/src/runtime/connectors/google-mcp.mjs +10 -12
  265. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  266. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  268. package/src/runtime/office/com/com-adapter.mjs +17 -1
  269. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  270. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  271. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  272. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  273. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  274. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  275. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  276. package/src/runtime/session-search/session-search-db.mjs +73 -9
  277. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  278. package/src/runtime/shared/abort-race.mjs +89 -0
  279. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  280. package/src/runtime/shared/agent-kind.mjs +4 -3
  281. package/src/runtime/shared/bot-hops.mjs +62 -2
  282. package/src/runtime/shared/bounded-download.mjs +8 -3
  283. package/src/runtime/shared/config.mjs +51 -35
  284. package/src/runtime/shared/connector-accounts.mjs +20 -10
  285. package/src/runtime/shared/connector-catalog.d.mts +33 -2
  286. package/src/runtime/shared/connector-catalog.mjs +166 -8
  287. package/src/runtime/shared/connector-triggers.mjs +22 -19
  288. package/src/runtime/shared/defaults-separation.mjs +2 -14
  289. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  290. package/src/runtime/shared/developer-options.mjs +32 -1
  291. package/src/runtime/shared/err-text.mjs +24 -15
  292. package/src/runtime/shared/file-permissions.d.mts +0 -8
  293. package/src/runtime/shared/file-permissions.mjs +0 -11
  294. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  295. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  296. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  297. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  298. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  299. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  300. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  301. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  302. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  303. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  304. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  305. package/src/runtime/shared/mixy-presence.mjs +22 -0
  306. package/src/runtime/shared/orchestration.d.mts +2 -0
  307. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  308. package/src/runtime/shared/profile-config.cjs +1 -1
  309. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  310. package/src/runtime/shared/prompt-origin.mjs +21 -0
  311. package/src/runtime/shared/provider-accounts.mjs +231 -59
  312. package/src/runtime/shared/provider-api-key.mjs +19 -8
  313. package/src/runtime/shared/provider-display-name.mjs +29 -0
  314. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  315. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  316. package/src/runtime/shared/routine-trigger.mjs +52 -0
  317. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  318. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  319. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  320. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  321. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  322. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  323. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  324. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  325. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  326. package/src/runtime/shared/user-data-guard.mjs +30 -2
  327. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  328. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  329. package/src/runtime/terminal-bridge/client.mjs +10 -1
  330. package/src/runtime/visualize-bridge/client.mjs +27 -8
  331. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  332. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  333. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  334. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  335. package/src/session-runtime/agent-home-delivery.mjs +486 -179
  336. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  337. package/src/session-runtime/agent-task-sessions.mjs +561 -199
  338. package/src/session-runtime/boot/apis.mjs +9 -0
  339. package/src/session-runtime/boot/begin.mjs +3 -2
  340. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  341. package/src/session-runtime/boot/providers.mjs +3 -0
  342. package/src/session-runtime/boot/tools.mjs +8 -1
  343. package/src/session-runtime/bot-chain.mjs +32 -0
  344. package/src/session-runtime/bot-status.mjs +7 -4
  345. package/src/session-runtime/channel-config-api.mjs +63 -17
  346. package/src/session-runtime/config-lifecycle.mjs +6 -0
  347. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  348. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  349. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  350. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  351. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  352. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  353. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  354. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  355. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  356. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  357. package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
  358. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  359. package/src/session-runtime/mixy-heartbeat.mjs +927 -0
  360. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  361. package/src/session-runtime/notification-bus.mjs +31 -117
  362. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  363. package/src/session-runtime/notification-listeners.mjs +76 -0
  364. package/src/session-runtime/owned-automation-run.mjs +35 -13
  365. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  366. package/src/session-runtime/provider-auth/login.mjs +7 -0
  367. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  368. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  369. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  370. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  371. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  372. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  373. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  374. package/src/session-runtime/routine-home-turn.mjs +33 -6
  375. package/src/session-runtime/routine-owner.mjs +40 -9
  376. package/src/session-runtime/schedule-session-run.mjs +7 -3
  377. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  378. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  379. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  380. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  381. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  382. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  383. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  384. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  385. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  386. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  387. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  388. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  389. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  390. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  391. package/src/session-runtime/services/channel-admin.mjs +73 -31
  392. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  393. package/src/session-runtime/services/channel-worker.mjs +1 -1
  394. package/src/session-runtime/services/provider-admin.mjs +128 -25
  395. package/src/session-runtime/services/schedule-access.mjs +14 -0
  396. package/src/session-runtime/services/seeds.mjs +0 -28
  397. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  398. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  399. package/src/session-runtime/session-create/session-options.mjs +8 -4
  400. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  401. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  402. package/src/session-runtime/settings-system-api.mjs +28 -1
  403. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  404. package/src/session-runtime/setup-tool/executor.mjs +187 -29
  405. package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
  406. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  407. package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
  408. package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
  409. package/src/session-runtime/task-session-route.mjs +99 -0
  410. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  411. package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
  412. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  413. package/src/session-runtime/tool-surface.mjs +4 -0
  414. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  415. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  416. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  417. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  418. package/src/session-runtime/workspace/transitions.mjs +10 -2
  419. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  420. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  421. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  422. package/src/standalone/channel-transport.mjs +1 -1
  423. package/src/standalone/daemon-channels-loader.mjs +10 -8
  424. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  425. package/src/standalone/daemon-session-control/request.mjs +51 -0
  426. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  427. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  428. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  429. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  430. package/src/standalone/daemon-session-control.mjs +161 -157
  431. package/src/standalone/daemon.mjs +33 -2
  432. package/src/standalone/session-client.mjs +7 -3
  433. package/src/standalone/session-protocol.mjs +10 -0
  434. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  435. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  436. package/src/standalone/session-service/quota-resume.mjs +3 -0
  437. package/src/standalone/session-service/retention/busy.mjs +65 -1
  438. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  439. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  440. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  441. package/src/standalone/session-service.mjs +13 -0
  442. package/src/standalone/session-tool-output.mjs +21 -10
  443. package/src/tui/app/model-options.mjs +4 -27
  444. package/src/tui/dist/index.mjs +81 -65
  445. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  446. package/src/tui/session/initial-state.mjs +3 -0
  447. package/src/tui/session/notice-surface.mjs +23 -1
  448. package/src/tui/session/queue-helpers.mjs +3 -42
  449. package/src/tui/session/session-api/integrations.mjs +1 -0
  450. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  451. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  452. package/src/tui/session/session-api/routes-write.mjs +5 -1
  453. package/src/tui/session/session-api/routes.mjs +1 -0
  454. package/src/tui/session/session-api/settings.mjs +30 -2
  455. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  456. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  457. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  458. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  459. package/src/tui/session/session-flow/drain.mjs +118 -149
  460. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  462. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  463. package/src/tui/session/tool-approval-request.mjs +39 -0
  464. package/src/tui/session/tool-approval-settle.mjs +62 -0
  465. package/src/tui/session/tool-approval.mjs +69 -115
  466. package/src/tui/session/tui-steering-persist.mjs +8 -1
  467. package/src/tui/session/turn-stream.mjs +36 -3
  468. package/src/tui/session/turn.mjs +23 -2
  469. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  471. package/scripts/code-graph-description-contract.mjs +0 -188
  472. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -194,38 +194,63 @@ async function archiveIndexFor(path, handle) {
194
194
  return entries;
195
195
  }
196
196
 
197
- /** One page of a session's archive sidecar (see pageFromSegments). The
198
- * open handle pins the file a concurrent atomic replace may swap out, so
199
- * the index and the segment reads describe the same bytes. */
200
- export async function readCompactionArchivePage(storeDir, sessionId, range = {}) {
197
+ /** Runs `use(entries, load)` over a session's archive sidecar: its segment
198
+ * index and a loader of one segment's items. The open handle pins the file
199
+ * a concurrent atomic replace may swap out, so the index and the segment
200
+ * reads describe the same bytes. `fallback` when the archive is absent or
201
+ * unreadable (display-only history: it shows nothing then). */
202
+ async function withArchive(storeDir, sessionId, fallback, use) {
201
203
  const path = compactionArchivePath(storeDir, sessionId);
202
- if (!path) return { items: [], hasOlder: false };
204
+ if (!path) return fallback;
203
205
  let handle;
204
206
  try {
205
207
  handle = await open(path, 'r');
206
208
  } catch {
207
- // Display-only history: an absent or unreadable archive shows nothing.
208
209
  archiveIndexes.delete(path);
209
- return { items: [], hasOlder: false };
210
+ return fallback;
210
211
  }
211
212
  try {
212
213
  const entries = await archiveIndexFor(path, handle);
213
- return await pageFromSegments(
214
- entries,
215
- async (entry) => {
216
- const buffer = Buffer.alloc(entry.length);
217
- await handle.read(buffer, 0, entry.length, entry.start);
218
- return segmentItems(buffer.toString('utf8'));
219
- },
220
- range
221
- );
214
+ return await use(entries, async (entry) => {
215
+ const buffer = Buffer.alloc(entry.length);
216
+ await handle.read(buffer, 0, entry.length, entry.start);
217
+ return segmentItems(buffer.toString('utf8'));
218
+ });
222
219
  } catch {
223
- return { items: [], hasOlder: false };
220
+ return fallback;
224
221
  } finally {
225
222
  await handle.close().catch(() => {});
226
223
  }
227
224
  }
228
225
 
226
+ /** One page of a session's archive sidecar (see pageFromSegments). */
227
+ export function readCompactionArchivePage(storeDir, sessionId, range = {}) {
228
+ return withArchive(storeDir, sessionId, { items: [], hasOlder: false }, (entries, load) =>
229
+ pageFromSegments(entries, load, range)
230
+ );
231
+ }
232
+
233
+ /** Hands every visible segment of the archive (numbered in (after, before),
234
+ * oldest first) to `visit` as its items, one segment at a time, so a whole
235
+ * walk never holds more than one segment. True when the walk completed;
236
+ * false when the archive was absent or unreadable (the visits made so far
237
+ * are then incomplete). */
238
+ export function visitCompactionArchive(
239
+ storeDir,
240
+ sessionId,
241
+ { after = 0, before = Number.POSITIVE_INFINITY } = {},
242
+ visit
243
+ ) {
244
+ return withArchive(storeDir, sessionId, false, async (entries, load) => {
245
+ for (const entry of entries) {
246
+ const { seq, count } = entry.head;
247
+ if (seq <= after || seq >= before || count < 1) continue;
248
+ visit(await load(entry));
249
+ }
250
+ return true;
251
+ });
252
+ }
253
+
229
254
  // Delete fence: every deletion bumps the session's epoch. A writer captures
230
255
  // the epoch before it starts and publishes only if it is unchanged at the
231
256
  // (synchronous) rename, so a delete that lands while a segment is being
@@ -11,6 +11,7 @@ import { createEagerEntry, runEagerEntry } from './eager-dispatch/entry.mjs';
11
11
  import { executeTool } from './loop/tool-exec.mjs';
12
12
  import { isParallelDispatchable } from './loop/tool-helpers.mjs';
13
13
  import { resolveExecutionToolCall } from './loop/tool-call-resolution.mjs';
14
+ import { turnLimitsOf } from './loop/turn-limits.mjs';
14
15
 
15
16
  export function createEagerDispatcher({
16
17
  tools,
@@ -23,6 +24,7 @@ export function createEagerDispatcher({
23
24
  getIterations,
24
25
  getNextIteration,
25
26
  repeatFailLimit,
27
+ isTurnLimitNoticed = () => false,
26
28
  executeToolFn = executeTool,
27
29
  }) {
28
30
  const pending = new Map();
@@ -37,6 +39,9 @@ export function createEagerDispatcher({
37
39
  repeatFailLimit,
38
40
  });
39
41
  const barriers = createEagerBarriers({ cwd });
42
+ // A capped turn runs its calls in order, so none past its cap starts early
43
+ // (loop/turn-limits.mjs).
44
+ const capped = turnLimitsOf(opts) !== null;
40
45
  const execute = (call, entry) =>
41
46
  executeToolFn(call.name, call.arguments, cwd, sessionId, sessionRef, {
42
47
  toolCallId: call.id,
@@ -52,6 +57,9 @@ export function createEagerDispatcher({
52
57
  const startEagerTool = (wireCall) => {
53
58
  // Streamed call_tool requests are admitted, ordered and executed as their
54
59
  // targets; a projection error is answered by the serial batch instead.
60
+ // Once the turn was told to reply (any limit), no call runs: the batch
61
+ // refuses them, so none may start while the wrap-up response streams.
62
+ if (capped || isTurnLimitNoticed()) return null;
55
63
  const call = resolveExecutionToolCall(wireCall, sessionRef);
56
64
  if (!call?.id || pending.has(call.id) || !isParallelDispatchable(call.name)) return null;
57
65
  const admitted = admission.admit(call);
@@ -14,17 +14,22 @@
14
14
  // allowed, payments and permanent deletes denied. Group-room members register
15
15
  // a port here (session-runtime/held-requests.mjs) that turns their asks into
16
16
  // held requests when no approval card is attached.
17
+ // The "Allow all actions" developer option (shared/developer-options.mjs)
18
+ // skips every ask below, the bot's per-action `ask` mode and a hook `ask`
19
+ // included, except a payment or permanent delete; a `block` and a hook `deny`
20
+ // still deny.
17
21
  import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../agent-home.mjs';
18
22
  import { serverConnector } from '../../mcp/server-connectors.mjs';
19
23
  import { readAgentDefinitionKind } from '../../../../shared/agent-kind.mjs';
20
24
  import { isContactAgentId } from '../../../../shared/agent-route-config.mjs';
25
+ import { developerOptionEnabledStrict } from '../../../../shared/developer-options.mjs';
21
26
  import { mixdogRoot, resolvePluginData } from '../../../../shared/plugin-paths.mjs';
22
27
  import { roomMemberOfSession } from '../agent-room.mjs';
23
28
  import { agentDisplayName } from '../../context/role-instructions.mjs';
24
29
  import { delegationOf } from './delegation.mjs';
25
30
  import { setUnattendedApprovalPort, unattendedApprovalPort } from './unattended-port.mjs';
26
- import { classifyAsDelete, classifyOutboundCall, isRememberableAction } from './outbound-policy.mjs';
27
- import { humanGateStore } from './store.mjs';
31
+ import { classifyAsDelete, classifyOutboundCall, isAlwaysAskAction, isRememberableAction } from './outbound-policy.mjs';
32
+ import { DEV_ALLOW_ALL_DECISION, humanGateStore } from './store.mjs';
28
33
  import {
29
34
  AUTOMATION_GRANT_REASON,
30
35
  RUN_FORBIDDEN_REASON,
@@ -33,7 +38,7 @@ import {
33
38
  savesAutomation,
34
39
  } from './run-policy.mjs';
35
40
 
36
- export const OUTBOUND_APPROVAL_REASON = 'This sends or posts outside Mixdog; it needs your approval.';
41
+ const OUTBOUND_APPROVAL_REASON = 'This sends or posts outside Mixdog; it needs your approval.';
37
42
  export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action.";
38
43
  /** Outbound grants may be remembered for this agent, never for every agent;
39
44
  * payments and deletes never. */
@@ -93,7 +98,8 @@ const LEAD_NAME = 'Lead';
93
98
  * bot-created task session, a bot's home chat or room member session) and
94
99
  * the names from there down to the asker ("Lead › Reviewer", "사무관 ›
95
100
  * Worker"). Only where the card shows: no bot identity, grant or remember
96
- * scope follows from it. Null for a top-level session. */
101
+ * scope follows from it. Null for a top-level session (whose requests show
102
+ * in the chats handing it work instead: tui/session/tool-approval.mjs). */
97
103
  export function approvalSurface(sessionId) {
98
104
  const delegation = delegationOf(sessionId);
99
105
  if (delegation.state !== 'resolved') return null;
@@ -105,6 +111,22 @@ export function approvalSurface(sessionId) {
105
111
 
106
112
  const actionOf = (decision) => String(decision?.action || decision?.decision || '').toLowerCase();
107
113
 
114
+ /** Whether the "Allow all actions" developer option is on (default off).
115
+ * Fail-closed: an unreadable, missing or malformed config is off (the call
116
+ * asks), and a value restored from a backup never counts. */
117
+ export const allowAllActionsEnabled = () => developerOptionEnabledStrict('allowAllActions');
118
+
119
+ /** Whether the option lets a call of this classification (or none) run without
120
+ * a card: every ask, never a classified payment or permanent delete. */
121
+ const skipsApproval = (classified) => !isAlwaysAskAction(classified?.category) && allowAllActionsEnabled();
122
+
123
+ /** The hook decision once its card is skipped: an `ask` runs as an allow,
124
+ * keeping any rewrite it carried. */
125
+ function withoutAsk(decision) {
126
+ if (actionOf(decision) !== 'ask') return decision;
127
+ return { ...decision, action: decision.name || decision.args ? 'modify' : 'allow' };
128
+ }
129
+
108
130
  async function mcpAnnotations(name, scopeId) {
109
131
  if (!String(name).startsWith('mcp__')) return undefined;
110
132
  try {
@@ -135,7 +157,13 @@ async function describeEmails(call, outbound, ctx) {
135
157
  const { executeMcpTool } = await import('../../mcp/client.mjs');
136
158
  const ids = outbound.category === 'trash' && Array.isArray(call.args?.ids) ? call.args.ids.map(String) : [];
137
159
  const shown = ids.slice(0, PREVIEW_MESSAGES);
138
- const raw = await executeMcpTool(name, { ids: shown }, { scopeId: ctx.mcpScopeId });
160
+ // The account the call acts as (one server holds every Gmail account).
161
+ const chosen = typeof call.args?.account === 'string' ? call.args.account.trim() : '';
162
+ const raw = await executeMcpTool(
163
+ name,
164
+ { ids: shown, ...(chosen ? { account: chosen } : {}) },
165
+ { scopeId: ctx.mcpScopeId }
166
+ );
139
167
  const { account, emails } = JSON.parse(raw);
140
168
  const messages = (Array.isArray(emails) ? emails : []).map((mail) =>
141
169
  [mail.from, mail.subject, String(mail.date || '').slice(0, 10)].filter(Boolean).join(' · ')
@@ -185,11 +213,17 @@ export async function humanGateDecision(call, decision, ctx = {}) {
185
213
  const outbound = classified && actingBotId(ctx.sessionId) ? classified : null;
186
214
  if (!outbound) {
187
215
  if (action !== 'ask') return { decision, request: null, gate: null };
216
+ const gate = { agentId, kind: 'approval', tool: String(call?.name || ''), target: '' };
217
+ if (skipsApproval(classified)) {
218
+ const store = ctx.store || (await humanGateStore());
219
+ await store.recordAudit({ ...gate, decision: DEV_ALLOW_ALL_DECISION, device: '' });
220
+ return { decision: withoutAsk(decision), request: null, gate: null };
221
+ }
188
222
  return {
189
223
  // Saving a schedule or webhook says plainly what the approval grants.
190
224
  decision: savesAutomation(call) ? { ...decision, reason: AUTOMATION_GRANT_REASON } : decision,
191
225
  request: { kind: 'approval', agentId, remember: ['once'] },
192
- gate: { agentId, kind: 'approval', tool: String(call?.name || ''), target: '' },
226
+ gate,
193
227
  };
194
228
  }
195
229
  const gate = { agentId, kind: 'outbound', action: outbound.category, tool: outbound.tool, target: outbound.target };
@@ -204,10 +238,9 @@ export async function humanGateDecision(call, decision, ctx = {}) {
204
238
  editable: outbound.editable,
205
239
  details: outbound.details,
206
240
  },
207
- remember: ['once'],
241
+ // "Don't ask again" is the bot's permission.
242
+ remember: outboundRemember(outbound.category),
208
243
  };
209
- // "Don't ask again" is the bot's permission.
210
- request.remember = outboundRemember(outbound.category);
211
244
  const store = ctx.store || (await humanGateStore());
212
245
  const mode = await store.modeFor({ agentId, action: outbound.category });
213
246
  const base = decision && typeof decision === 'object' ? decision : {};
@@ -215,6 +248,11 @@ export async function humanGateDecision(call, decision, ctx = {}) {
215
248
  await store.recordAudit({ ...gate, decision: 'blocked', device: '' });
216
249
  return { decision: { ...base, action: 'deny', reason: BLOCKED_ACTION_REASON }, request: null, gate: null };
217
250
  }
251
+ // The bot's `ask` mode (and a hook's `ask`) is bypassed; a payment or delete is not.
252
+ if (skipsApproval(outbound)) {
253
+ await store.recordAudit({ ...gate, decision: DEV_ALLOW_ALL_DECISION, device: '' });
254
+ return { decision: withoutAsk(decision), request: null, gate: null };
255
+ }
218
256
  if (action !== 'ask' && mode === 'allow') {
219
257
  await store.recordAudit({ ...gate, decision: 'remembered', device: '' });
220
258
  // The remembered grant answers this call: no later layer asks it again.
@@ -39,6 +39,12 @@ export function isRememberableAction(kind) {
39
39
  return ACTION_KINDS.includes(kind) && !UNREMEMBERED_ACTIONS.has(kind);
40
40
  }
41
41
 
42
+ /** Whether `kind` is a payment or permanent delete: it asks every time,
43
+ * whatever else is allowed (also by the "Allow all actions" developer option). */
44
+ export function isAlwaysAskAction(kind) {
45
+ return UNREMEMBERED_ACTIONS.has(kind);
46
+ }
47
+
42
48
  const GITHUB_OUTBOUND = new Map([
43
49
  ['issue.comment', 'comment'],
44
50
  ['pr.comment', 'comment'],
@@ -68,8 +74,6 @@ const MCP_OUTBOUND = new Map([
68
74
  'slack_post_message',
69
75
  'slack_reply_to_thread',
70
76
  'chat_post_message',
71
- 'discord_send_message',
72
- 'telegram_send_message',
73
77
  ].map((name) => [name, 'message']),
74
78
  ...['create_post', 'publish_post', 'create_tweet', 'post_tweet', 'send_tweet'].map((name) => [name, 'publish']),
75
79
  ['submit_form', 'form'],
@@ -12,10 +12,23 @@
12
12
  // the scopes it allows and can leave out `always`. Nothing here ever carries a
13
13
  // secret value: a decision that names one is stripped to its verdict.
14
14
 
15
- export const HUMAN_REQUEST_KINDS = Object.freeze(['approval', 'outbound', 'secret', 'takeover', 'permission']);
16
- export const HUMAN_RESPONSES = Object.freeze(['approve', 'deny', 'edit-and-approve', 'provide-secret', 'skip', 'done']);
17
- export const REMEMBER_SCOPES = Object.freeze(['once', 'agent', 'always']);
15
+ const HUMAN_REQUEST_KINDS = Object.freeze(['approval', 'outbound', 'secret', 'takeover', 'permission']);
16
+ const HUMAN_RESPONSES = Object.freeze(['approve', 'deny', 'edit-and-approve', 'provide-secret', 'skip', 'done']);
17
+ const REMEMBER_SCOPES = Object.freeze(['once', 'agent', 'always']);
18
18
  export const SECRET_REQUEST_TIMEOUT_MS = 120_000;
19
+ /** What a secret's bound field is: a sign-in form (아이디 + 비밀번호), a lone
20
+ * password, a new password, or a one-time code; '' when unknown (computer). */
21
+ export const SECRET_FORMS = Object.freeze(['login', 'password', 'new-password', 'otp']);
22
+ /** Every result a secret handoff (and a declined or expired takeover) gives
23
+ * the agent: facts, never a verdict about the site. */
24
+ export const HANDOFF_OUTCOMES = Object.freeze([
25
+ 'entered',
26
+ 'user_skipped',
27
+ 'no_field',
28
+ 'page_changed',
29
+ 'blocked',
30
+ 'expired',
31
+ ]);
19
32
 
20
33
  const RESPONSES_BY_KIND = Object.freeze({
21
34
  approval: ['approve', 'deny'],
@@ -49,7 +62,7 @@ const text = (value, limit = TEXT_LIMIT) => (typeof value === 'string' ? value.t
49
62
 
50
63
  /** The human kind of a request; anything else (ask_user's 'question', a
51
64
  * hook's bare ask) is an ordinary approval for responses. */
52
- export function humanRequestKind(request) {
65
+ function humanRequestKind(request) {
53
66
  return HUMAN_REQUEST_KINDS.includes(request?.kind) ? request.kind : 'approval';
54
67
  }
55
68
 
@@ -103,11 +116,21 @@ export function humanRequestFields(input = {}) {
103
116
  }
104
117
  if (kind === 'secret') {
105
118
  const secret = isRecord(input.secret) ? input.secret : {};
119
+ // The bound page's origin and what its field is (the desktop read it at
120
+ // request time, ask-user-tool.mjs): the card's title and inputs; no value.
121
+ const origin = loginPageUrl(secret.origin)?.origin || '';
122
+ const form = SECRET_FORMS.includes(secret.form) ? secret.form : '';
123
+ // A code's length is the bound field's own (credential-autofill.ts
124
+ // codeLength); absent, it stays unspecified.
125
+ const length = Number.isInteger(secret.length) && secret.length > 0 ? secret.length : 0;
106
126
  fields.secret = {
107
127
  requestId: text(secret.requestId),
108
128
  surface: secret.surface === 'computer' ? 'computer' : 'browser',
109
129
  target: text(secret.target),
110
130
  field: text(secret.field),
131
+ ...(origin ? { origin } : {}),
132
+ ...(form ? { form } : {}),
133
+ ...(form === 'otp' && length ? { length } : {}),
111
134
  };
112
135
  fields.args = { surface: fields.secret.surface, target: fields.secret.target, field: fields.secret.field };
113
136
  fields.remember = ['once'];
@@ -132,12 +155,21 @@ export function humanRequestFields(input = {}) {
132
155
  // A login takeover (purpose 'login') opens the desktop's sign-in window on
133
156
  // its validated page instead of handing over the side panel.
134
157
  const login = surface === 'browser' && takeover.purpose === 'login' ? loginPageUrl(takeover.url) : null;
158
+ // The saved logins of the sign-in page's origin: opaque ids and masked
159
+ // labels only (profile-import.ts credentialSuggestions), never a password.
160
+ const accounts = (login && Array.isArray(takeover.accounts) ? takeover.accounts : [])
161
+ .filter((account) => isRecord(account) && token(account.id, 128) && text(account.label, 120))
162
+ .map((account) => ({ id: token(account.id, 128), label: text(account.label, 120) }));
163
+ // The page those logins may be filled into, bound by the desktop at
164
+ // request time (ask-user-tool.mjs fillId): without it none is offered.
165
+ const fillId = /^secret_[A-Za-z0-9-]{8,80}$/.test(text(takeover.fillId, 100)) ? text(takeover.fillId, 100) : '';
135
166
  fields.takeover = {
136
167
  surface,
137
168
  instructions: text(takeover.instructions, 2_000),
138
169
  ...(text(takeover.target) ? { target: text(takeover.target) } : {}),
139
170
  ...identity,
140
171
  ...(login ? { purpose: 'login', url: login.url, origin: login.origin } : {}),
172
+ ...(accounts.length && fillId ? { accounts, fillId } : {}),
141
173
  };
142
174
  fields.remember = ['once'];
143
175
  }
@@ -173,12 +173,24 @@ export async function setAgentPermission(agentId, action, mode, options) {
173
173
  return store.permissions(agent);
174
174
  }
175
175
 
176
- const BROWSER_AUDIT_DECISIONS = Object.freeze(['approve', 'deny', 'granted', 'remembered', 'run-allowed', 'blocked']);
176
+ /** The audit decision of a call the "Allow all actions" developer option let through unasked. */
177
+ export const DEV_ALLOW_ALL_DECISION = 'dev-allow-all';
178
+
179
+ const BROWSER_AUDIT_DECISIONS = Object.freeze([
180
+ 'approve',
181
+ 'deny',
182
+ 'granted',
183
+ 'remembered',
184
+ 'run-allowed',
185
+ 'blocked',
186
+ DEV_ALLOW_ALL_DECISION,
187
+ ]);
177
188
 
178
189
  /** Audit one Browser Use approval of a bot (capability recordAgentApprovalAudit):
179
190
  * the bot, the action kind and what happened — `approve` / `deny` for the card,
180
191
  * `granted` when "always allow" was saved, `remembered` when a saved allow let
181
- * a step run unasked. Never a page address, target or value. */
192
+ * a step run unasked, `dev-allow-all` when the developer option did. Never a
193
+ * page address, target or value. */
182
194
  export async function recordAgentApprovalAudit(agentId, action, decision) {
183
195
  const agent = requiredAgentId(agentId);
184
196
  if (!ACTION_KINDS.includes(action)) throw new Error(`action must be one of ${ACTION_KINDS.join(', ')}`);
@@ -62,6 +62,12 @@ function assistantEchoFields(payloads) {
62
62
  };
63
63
  }
64
64
 
65
+ // The tail reminder the response was generated under (send-with-recovery.mjs),
66
+ // kept verbatim so a turn-scoped provider replays it byte-identically.
67
+ export function turnReminderField(resp) {
68
+ return typeof resp?.turnReminder === 'string' && resp.turnReminder ? { turnReminder: resp.turnReminder } : {};
69
+ }
70
+
65
71
  // Intermediate (non-tool) assistant turn: max-output partials, refusal
66
72
  // narration, provider continuations, and the terminal turn committed before a
67
73
  // steering drain. Returns null when the turn carries nothing committable — a
@@ -89,6 +95,7 @@ export function buildIntermediateAssistantMessage(resp, opts) {
89
95
  role: 'assistant',
90
96
  content,
91
97
  ...assistantEchoFields(payloads),
98
+ ...turnReminderField(resp),
92
99
  ...(stopReason ? { stopReason } : {}),
93
100
  ...(terminationReason ? { terminationReason } : {}),
94
101
  },
@@ -108,6 +115,7 @@ export function buildToolCallAssistantMessage(response, { calls, opts }) {
108
115
  content: response.content || '',
109
116
  toolCalls: compactToolCallsForHistory(calls, { deferBodies: true }),
110
117
  ...assistantEchoFields(assistantEchoPayloads(response, { keepAssistantBlocks: true })),
118
+ ...turnReminderField(response),
111
119
  },
112
120
  opts
113
121
  );
@@ -21,6 +21,7 @@ import { isAgentModeSession } from '../agent-mode.mjs';
21
21
  import { providerInputExcludesCache, providerNativePdf } from '../../providers/registry.mjs';
22
22
  import { contentMediaBytes, contentMediaCount } from '../../providers/media-normalization.mjs';
23
23
  import { splitProtectedContext } from '../compact/messages.mjs';
24
+ import { withLiveCompactionConfig } from '../../runtime-core/compaction-config-keys.mjs';
24
25
 
25
26
  // Unified context-share rule (compact/constants.mjs CONTEXT_SHARE_RATIO): the
26
27
  // post-compaction target is 25% of the boundary/context window. One
@@ -394,13 +395,6 @@ export function invalidateProviderContextBaseline(sessionRef) {
394
395
  delete sessionRef.contextPressureUnanchoredReason;
395
396
  }
396
397
 
397
- // A baseline is refreshed on every provider_send/turn-end. When the transcript
398
- // has grown but no fresh recording landed for this long, recording is failing
399
- // (zero/absent usage keeps the OLD baseline because record() only returns
400
- // false) — distrust it and fall back to the estimate. An idle session whose
401
- // transcript did NOT grow keeps its baseline regardless of age.
402
- const BASELINE_MAX_STALE_GROWTH_MS = 30 * 60 * 1000;
403
-
404
398
  /**
405
399
  * Does this anchor still describe this message prefix?
406
400
  *
@@ -480,17 +474,19 @@ function providerBaselinePressureTokens(messages, sessionRef, policy, { includeC
480
474
  ? Math.max(0, tokens - Math.round(storedRequestReserve) + currentRequestReserve)
481
475
  : tokens + currentRequestReserve;
482
476
  }
483
- // Staleness means a baseline that stopped being refreshed WHILE the session
484
- // kept working — never age on the wall clock. Measured against `now`, this
485
- // discarded the reading of every session that simply sat unopened for a
486
- // while (88 of 983 stored sessions), and the whole-transcript estimate that
487
- // replaced it was the less accurate number in both directions: 1.12x over
488
- // on the median, 0.82x under on a session already past its window.
489
- // Comparing against the session's own last activity keeps the rule aimed at
490
- // its actual target — a live session whose usage recording is failing while
491
- // its transcript grows — and leaves a session at rest on its measurement.
492
- const activityAt = Math.max(Number(sessionRef.updatedAt) || 0, Number(sessionRef.lastContextTokensUpdatedAt) || 0);
493
- if (messages.length > count && baselineAt > 0 && activityAt - baselineAt > BASELINE_MAX_STALE_GROWTH_MS) return null;
477
+ // Staleness means usage recording is failing: a provider request completed
478
+ // AFTER this reading without replacing it (every send records its usage
479
+ // after it starts, so a working recorder always leaves the reading newer
480
+ // than the last send) — zero/absent usage keeps the OLD baseline because
481
+ // record() only returns false. Never age on a clock: measured against `now`
482
+ // it discarded the reading of every session that sat unopened for a while
483
+ // (88 of 983 stored sessions), and measured against the session's last
484
+ // activity it discarded it whenever anything was appended more than half an
485
+ // hour after the last request — a user back from a break, an agent's result
486
+ // posted to a waiting Lead — so the next send compacted on the
487
+ // whole-transcript estimate, far above the provider's own count.
488
+ const lastSendAt = Number(sessionRef.lastProviderSendAt) || 0;
489
+ if (messages.length > count && baselineAt > 0 && lastSendAt > baselineAt) return null;
494
490
  try {
495
491
  // Baseline tokens are authoritative provider billing; only the growth
496
492
  // after the baseline is a local estimate and needs billing calibration.
@@ -795,9 +791,9 @@ function compactionTelemetryRecord(sessionRef, policy, meta, prev) {
795
791
  if (terminalSuccess) return null;
796
792
  return present ? (next ?? null) : (previous ?? null);
797
793
  };
798
- return {
799
- ...prev,
800
- auto: policy.auto !== false,
794
+ // Config-owned fields stay the live ones (withLiveCompactionConfig): `policy`
795
+ // was resolved before a compaction that a reload may have outlived.
796
+ return withLiveCompactionConfig(prev, {
801
797
  requestReserveTokens: policy.requestReserveTokens || 0,
802
798
  reserveTokens: policy.reserveTokens || 0,
803
799
  boundaryTokens: policy.boundaryTokens || null,
@@ -848,7 +844,7 @@ function compactionTelemetryRecord(sessionRef, policy, meta, prev) {
848
844
  compactCount: (prev.compactCount || 0) + (changed ? 1 : 0),
849
845
  singleShotConsumed:
850
846
  policy.singleShot === true && meta.stage === 'compacting' ? true : prev.singleShotConsumed === true,
851
- };
847
+ });
852
848
  }
853
849
 
854
850
  // Postmortem ring buffer: the per-check telemetry record is overwritten on
@@ -18,6 +18,9 @@ import { summarizeOnSharedPrefix } from '../compact/forked-summary.mjs';
18
18
  import { normalizeCompactionSummaryMode } from '../compact/summary-mode.mjs';
19
19
  import { compactionCacheWarm, lighterSubscriptionModel } from '../compact/summary-route.mjs';
20
20
  import { retireCompactionFork } from '../side-question-store.mjs';
21
+ import { buildSkillToolEnvelope, collectSkillsCached, currentSkillDelivery } from '../../context/collect.mjs';
22
+ import { loadSkillToolDependencies } from '../../runtime-core/skill-tool-loading.mjs';
23
+ import { parseNativeToolSearchPayload } from './tool-helpers.mjs';
21
24
 
22
25
  // Conversation summarization triggers independently of the post-compact target:
23
26
  // at 5% of the window, capped so a large window does not carry tens of
@@ -262,11 +265,37 @@ function freshContextBudget({ compactPolicy, sessionRef, provider, compactBudget
262
265
  return { contextWindow, hardBudget, conversationInput, conversationTokens, conversationThresholdTokens };
263
266
  }
264
267
 
268
+ // Compact restores each active skill from its current source, as the Skill
269
+ // tool would deliver it in this session now (null: it no longer loads). The
270
+ // real pass also re-arms the skill's current tool dependencies on the session
271
+ // surface and answers the native tool-search payload they produce (or, with
272
+ // none, the Skill result summary); a preview never touches the surface and
273
+ // keeps the transcript's loader pairs.
274
+ function currentSkillResolver(sessionRef, { preview = false } = {}) {
275
+ const cwd = sessionRef?.cwd || null;
276
+ const config = loadConfig({ secrets: false });
277
+ const skills = collectSkillsCached(cwd);
278
+ return (name) => {
279
+ const delivery = currentSkillDelivery(name, cwd, config, skills);
280
+ if (!delivery) return null;
281
+ if (preview) return { body: delivery.body, native: undefined };
282
+ const { resource } = delivery;
283
+ const envelope = loadSkillToolDependencies(
284
+ buildSkillToolEnvelope(name, resource.content, resource.dir, resource),
285
+ sessionRef
286
+ );
287
+ const native = parseNativeToolSearchPayload('Skill', envelope.result);
288
+ return { body: delivery.body, native, summary: native?.summary ?? envelope.result };
289
+ };
290
+ }
291
+
265
292
  function freshContextBuildOptions(
266
- { compactPolicy = {}, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
293
+ { sessionRef, compactPolicy = {}, sessionId, goalReminderText, activeTurn, referenceLatestAttachments, preview },
267
294
  budget
268
295
  ) {
269
296
  return {
297
+ // A sessionless call (no session gates to apply) keeps the transcript copies.
298
+ currentSkill: sessionRef ? currentSkillResolver(sessionRef, { preview }) : null,
270
299
  reserveTokens: compactPolicy.reserveTokens,
271
300
  maxBudgetTokens: budget.hardBudget,
272
301
  force: true,
@@ -287,7 +316,7 @@ export function previewFreshContextCompaction(input) {
287
316
  const budget = freshContextBudget(input);
288
317
  const summaryTriggered = budget.conversationTokens >= budget.conversationThresholdTokens;
289
318
  const result = freshContextCompactMessages(input.messages, input.compactBudgetTokens, {
290
- ...freshContextBuildOptions(input, budget),
319
+ ...freshContextBuildOptions({ ...input, preview: true }, budget),
291
320
  preview: true,
292
321
  previewSummary: summaryTriggered,
293
322
  });
@@ -346,7 +375,7 @@ export async function runFreshContextCompact({
346
375
  });
347
376
  const summaryTriggered = conversationTokens >= conversationThresholdTokens;
348
377
  const buildOptions = freshContextBuildOptions(
349
- { compactPolicy, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
378
+ { sessionRef, compactPolicy, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
350
379
  { contextWindow, hardBudget }
351
380
  );
352
381
  const build = (handoffText) =>
@@ -55,6 +55,9 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
55
55
  cwd: resolveLiveToolCwd(cwd, sessionRef),
56
56
  iterations: 0,
57
57
  toolCallsTotal: 0,
58
+ // A capped turn (turn-limits.mjs): told to reply, and ended by calling tools anyway.
59
+ turnLimitNoticed: false,
60
+ turnLimitEnded: false,
58
61
  lastUsage: undefined,
59
62
  firstTurnUsage: undefined,
60
63
  response: undefined,
@@ -117,7 +120,9 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
117
120
  messages.push(message);
118
121
  try {
119
122
  opts.onToolResult?.(message);
120
- } catch {}
123
+ } catch {
124
+ // the tool-result observer is advisory
125
+ }
121
126
  };
122
127
  state.drainSteering = createSteeringDrain({
123
128
  messages,
@@ -147,6 +152,9 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
147
152
  * promote an empty/abnormal finish to an explicit Lead-facing error). */
148
153
  export function finishLoop(state) {
149
154
  const { response } = state;
155
+ // A reaction-only turn is a successful completion, never an empty finish.
156
+ const terminationReason = classifyTerminationReason(response, { sessionAgent: state.sessionAgent });
157
+ const reactionCompleted = terminationReason === 'empty' && state.noToolTurn.reactionCompleted;
150
158
  return {
151
159
  ...response,
152
160
  usage: state.lastUsage || response.usage,
@@ -157,7 +165,8 @@ export function finishLoop(state) {
157
165
  toolCallsTotal: state.toolCallsTotal,
158
166
  providerState: state.providerState,
159
167
  providerStateUpdated: state.providerStateUpdated,
160
- terminationReason: classifyTerminationReason(response, { sessionAgent: state.sessionAgent }),
168
+ terminationReason: reactionCompleted ? undefined : terminationReason,
169
+ ...(reactionCompleted ? { reactionCompleted: true } : {}),
161
170
  providerContinuations: state.noToolTurn.providerContinuations,
162
171
  ...(state.routeUsageHops?.length
163
172
  ? {
@@ -20,8 +20,11 @@ export function createTurnSegments({ messages, opts }) {
20
20
  // Record a mid-turn segment for the caller-facing aggregate and surface it
21
21
  // live for every session.
22
22
  // `committed: true` marks text that is the just-committed message's own.
23
+ // A committed message is whole, so the text after it is another message
24
+ // and a break keeps the aggregate from running the two together; a part
25
+ // that was cut off (max-output recovery) resumes right where it stopped.
23
26
  record(text, { committed = false } = {}) {
24
- parts.push(text);
27
+ parts.push(committed ? `${text.trimEnd()}\n\n` : text);
25
28
  try {
26
29
  opts.onAssistantText?.(text, { committed });
27
30
  } catch {