mixdog 1.3.5 → 1.4.1

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 (515) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +2 -11
  4. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/gate-local.mjs +16 -5
  7. package/scripts/latest-guard.mjs +21 -9
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +37 -37
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +123 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/native-release-status.mjs +2 -2
  14. package/scripts/post-release-check.mjs +1 -6
  15. package/scripts/pptx-kit-index.mjs +17 -4
  16. package/scripts/recall-history-cases.json +1 -1
  17. package/scripts/suite-health.mjs +20 -2
  18. package/scripts/test-data-dir.mjs +10 -7
  19. package/scripts/test.mjs +7 -11
  20. package/src/agents/reviewer/AGENT.md +7 -11
  21. package/src/agents/reviewer/agent.json +1 -1
  22. package/src/app.mjs +4 -0
  23. package/src/defaults/shipped-history.json +22 -1
  24. package/src/defaults/skills/browser-use/SKILL.md +62 -12
  25. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  26. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  27. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  28. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  29. package/src/defaults/skills/computer-use/SKILL.md +35 -9
  30. package/src/defaults/skills/setup/SKILL.md +22 -0
  31. package/src/defaults/skills/setup/references/actions.md +39 -5
  32. package/src/defaults/skills/setup/references/surfaces.md +11 -2
  33. package/src/defaults/skills/visualize/SKILL.md +2 -1
  34. package/src/lib/rules-builder.cjs +39 -20
  35. package/src/output-styles/common.md +2 -2
  36. package/src/output-styles/simple.md +1 -1
  37. package/src/rules/agent/AGENT.md +11 -10
  38. package/src/rules/lead/LEAD.md +23 -20
  39. package/src/rules/shared/05-tool-calls.md +5 -6
  40. package/src/rules/shared/10-tool-workflow.md +4 -0
  41. package/src/rules/shared/45-destructive.md +0 -2
  42. package/src/rules/shared/50-execution.md +2 -0
  43. package/src/rules/shared/60-verification.md +7 -5
  44. package/src/rules/shared/70-delivery.md +7 -9
  45. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
  46. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  47. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  48. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  49. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  50. package/src/runtime/agent/orchestrator/config.mjs +17 -1
  51. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  52. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +73 -68
  53. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +57 -2
  54. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  55. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
  56. package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
  57. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
  58. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +192 -133
  59. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
  60. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  63. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  66. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +159 -69
  71. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  72. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +70 -33
  73. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  74. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  75. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
  76. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  77. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  78. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  79. package/src/runtime/agent/orchestrator/providers/cursor.mjs +25 -13
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
  81. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  82. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
  83. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  84. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  85. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  87. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  88. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  89. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  90. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  91. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  92. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  93. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  106. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
  109. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  113. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
  117. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  118. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  119. package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -0
  120. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  121. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  122. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +49 -16
  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 +7 -6
  127. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +9 -3
  128. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
  131. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  132. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +798 -0
  134. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  135. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  136. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  137. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  138. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +280 -45
  139. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  140. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  141. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  142. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
  143. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
  144. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  145. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  146. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
  147. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
  148. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
  149. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
  150. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  151. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +51 -25
  153. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +37 -3
  154. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
  155. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
  156. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  157. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  158. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +31 -3
  159. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +20 -3
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +5 -4
  162. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  163. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  164. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +49 -21
  165. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  166. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
  167. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  168. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  172. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  173. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  174. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +24 -14
  175. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  176. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  177. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +130 -0
  178. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
  179. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +81 -8
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  181. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -8
  182. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
  183. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  184. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +9 -4
  185. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  187. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +21 -5
  188. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
  189. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +24 -9
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  191. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +150 -0
  192. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  193. package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
  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-delete.mjs +7 -0
  203. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +81 -22
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +92 -3
  209. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  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/transcript-restore/restore.mjs +79 -3
  214. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  217. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  218. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  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/spawn-env.mjs +4 -2
  225. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +31 -6
  226. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +110 -26
  227. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  228. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
  229. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  231. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  232. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  233. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  234. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  235. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  236. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  237. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  238. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  239. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  240. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  241. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
  243. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  244. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  245. package/src/runtime/browser-bridge/client.mjs +73 -8
  246. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  247. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  248. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  249. package/src/runtime/channels/lib/config.mjs +6 -61
  250. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  251. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  252. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  253. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  254. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  255. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  256. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  257. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  258. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  259. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  260. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  261. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  262. package/src/runtime/channels/lib/scheduler.mjs +33 -147
  263. package/src/runtime/channels/lib/schedules-db.mjs +34 -11
  264. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  265. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
  266. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  267. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  268. package/src/runtime/channels/lib/whisper-port.mjs +10 -1
  269. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  270. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  271. package/src/runtime/channels/lib/worker-main.mjs +10 -34
  272. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  273. package/src/runtime/computer-bridge/actions.mjs +9 -1
  274. package/src/runtime/computer-bridge/client.mjs +180 -8
  275. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  276. package/src/runtime/computer-bridge/error-recovery.mjs +28 -2
  277. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  278. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  279. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  280. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  281. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  282. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  283. package/src/runtime/connectors/google-mcp.mjs +10 -12
  284. package/src/runtime/local-provider/server-process.mjs +2 -1
  285. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  286. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  287. package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
  288. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  289. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  290. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  291. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  292. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  293. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  294. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  295. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  296. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  297. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  298. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  299. package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
  300. package/src/runtime/office/quality/design-review.mjs +1 -5
  301. package/src/runtime/office/quality/document-brief.mjs +1 -5
  302. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  303. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  304. package/src/runtime/session-search/session-search-db.mjs +73 -9
  305. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  306. package/src/runtime/shared/abort-race.mjs +89 -0
  307. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  308. package/src/runtime/shared/agent-kind.mjs +4 -3
  309. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  310. package/src/runtime/shared/bounded-download.mjs +8 -3
  311. package/src/runtime/shared/config.mjs +2 -13
  312. package/src/runtime/shared/connector-accounts.mjs +20 -10
  313. package/src/runtime/shared/connector-catalog.d.mts +29 -0
  314. package/src/runtime/shared/connector-catalog.mjs +150 -0
  315. package/src/runtime/shared/connector-triggers.mjs +22 -19
  316. package/src/runtime/shared/defaults-separation.mjs +2 -17
  317. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  318. package/src/runtime/shared/err-text.mjs +24 -15
  319. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  320. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  321. package/src/runtime/shared/file-permissions.d.mts +0 -8
  322. package/src/runtime/shared/file-permissions.mjs +0 -11
  323. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  324. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  325. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  326. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  327. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  328. package/src/runtime/shared/idle-auto-clear-gate.mjs +126 -0
  329. package/src/runtime/shared/llm/cost.mjs +5 -1
  330. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  331. package/src/runtime/shared/loopback-listener.mjs +25 -2
  332. package/src/runtime/shared/mixy-presence.mjs +22 -0
  333. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  334. package/src/runtime/shared/model-route-chain.mjs +22 -4
  335. package/src/runtime/shared/open-url.mjs +10 -0
  336. package/src/runtime/shared/orchestration.d.mts +2 -0
  337. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  338. package/src/runtime/shared/profile-config.cjs +1 -1
  339. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  340. package/src/runtime/shared/prompt-origin.mjs +21 -0
  341. package/src/runtime/shared/provider-accounts.mjs +7 -1
  342. package/src/runtime/shared/provider-display-name.mjs +29 -0
  343. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  344. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  345. package/src/runtime/shared/routine-trigger.mjs +10 -0
  346. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  347. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  348. package/src/runtime/shared/session-activity-signal.mjs +48 -1
  349. package/src/runtime/shared/session-scratch.mjs +37 -0
  350. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  351. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  352. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  353. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  354. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  355. package/src/runtime/shared/tool-result-summary.mjs +2 -15
  356. package/src/runtime/shared/tool-surface.mjs +3 -0
  357. package/src/runtime/shared/tool-work-units.mjs +2 -0
  358. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  359. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  360. package/src/runtime/shared/worktrees/git.mjs +15 -0
  361. package/src/runtime/shared/worktrees/manager.mjs +87 -38
  362. package/src/runtime/shared/worktrees/test-support.mjs +14 -13
  363. package/src/runtime/terminal-bridge/client.mjs +10 -1
  364. package/src/runtime/visualize-bridge/client.mjs +29 -8
  365. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  366. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  367. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  368. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  369. package/src/session-runtime/agent-home-delivery.mjs +489 -185
  370. package/src/session-runtime/agent-task-sessions.mjs +467 -229
  371. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  372. package/src/session-runtime/boot/apis.mjs +9 -0
  373. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  374. package/src/session-runtime/boot/providers.mjs +3 -0
  375. package/src/session-runtime/boot/tools.mjs +11 -2
  376. package/src/session-runtime/bot-status.mjs +12 -6
  377. package/src/session-runtime/bot-target.mjs +38 -0
  378. package/src/session-runtime/channel-config-api.mjs +6 -1
  379. package/src/session-runtime/config-lifecycle.mjs +6 -0
  380. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  381. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  382. package/src/session-runtime/conversation-import/discovery.mjs +271 -25
  383. package/src/session-runtime/conversation-import/service.mjs +26 -5
  384. package/src/session-runtime/handoff-attachments.mjs +82 -31
  385. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +144 -41
  386. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -0
  387. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  388. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
  389. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  390. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  391. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  392. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  393. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  394. package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
  395. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  396. package/src/session-runtime/mixy-heartbeat.mjs +153 -33
  397. package/src/session-runtime/model-route/set-route.mjs +11 -2
  398. package/src/session-runtime/notification-bus.mjs +31 -117
  399. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  400. package/src/session-runtime/notification-listeners.mjs +76 -0
  401. package/src/session-runtime/owned-automation-run.mjs +2 -1
  402. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  403. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  404. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  405. package/src/session-runtime/provider-models.mjs +4 -2
  406. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  407. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  408. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  409. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  410. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  411. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  412. package/src/session-runtime/resource-recall-api.mjs +6 -1
  413. package/src/session-runtime/routine-owner.mjs +40 -9
  414. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  415. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  416. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  417. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  418. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  419. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  420. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +13 -4
  421. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  422. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  423. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  424. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  425. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  426. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  427. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  428. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  429. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  430. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  431. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  432. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  433. package/src/session-runtime/services/channel-admin.mjs +7 -13
  434. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  435. package/src/session-runtime/services/provider-admin.mjs +2 -1
  436. package/src/session-runtime/services/seeds.mjs +0 -28
  437. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  438. package/src/session-runtime/session-create/session-options.mjs +1 -0
  439. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  440. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  441. package/src/session-runtime/settings-system-api.mjs +13 -1
  442. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  443. package/src/session-runtime/setup-tool/executor.mjs +145 -22
  444. package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
  445. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  446. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
  447. package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
  448. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  449. package/src/session-runtime/tool-policy/policy-stage.mjs +17 -2
  450. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  451. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  452. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
  453. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  454. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  455. package/src/session-runtime/workspace/transitions.mjs +25 -0
  456. package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
  457. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  458. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  459. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  460. package/src/standalone/channel-transport.mjs +1 -1
  461. package/src/standalone/daemon-channels-loader.mjs +10 -8
  462. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  463. package/src/standalone/daemon-session-control/request.mjs +51 -0
  464. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  465. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  466. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  467. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  468. package/src/standalone/daemon-session-control.mjs +142 -190
  469. package/src/standalone/daemon.mjs +26 -3
  470. package/src/standalone/session-client.mjs +7 -3
  471. package/src/standalone/session-protocol.mjs +5 -0
  472. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  473. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  474. package/src/standalone/session-service/quota-resume.mjs +3 -0
  475. package/src/standalone/session-service/retention/busy.mjs +65 -1
  476. package/src/standalone/session-service/session-calls/session-action.mjs +30 -2
  477. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  478. package/src/standalone/session-tool-output.mjs +15 -7
  479. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  480. package/src/tui/app/app-view/floating-panel.jsx +7 -4
  481. package/src/tui/app/model-options.mjs +3 -28
  482. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  483. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  484. package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
  485. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  486. package/src/tui/app/provider-setup-picker.mjs +9 -5
  487. package/src/tui/components/QuestionPanel.jsx +13 -4
  488. package/src/tui/dist/index.mjs +133 -92
  489. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  490. package/src/tui/session/initial-state.mjs +3 -0
  491. package/src/tui/session/notice-surface.mjs +7 -1
  492. package/src/tui/session/queue-helpers.mjs +3 -42
  493. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  494. package/src/tui/session/session-api/routes-write.mjs +5 -1
  495. package/src/tui/session/session-api/routes.mjs +1 -0
  496. package/src/tui/session/session-api/settings.mjs +17 -1
  497. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  498. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  499. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  500. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  501. package/src/tui/session/session-flow/drain.mjs +118 -149
  502. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  503. package/src/tui/session/session-flow/queue/take-entries.mjs +16 -2
  504. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  505. package/src/tui/session/tool-approval-request.mjs +39 -0
  506. package/src/tui/session/tool-approval-settle.mjs +62 -0
  507. package/src/tui/session/tool-approval.mjs +69 -115
  508. package/src/tui/session/tui-steering-persist.mjs +8 -1
  509. package/src/tui/session/turn-stream.mjs +19 -2
  510. package/src/tui/session/turn.mjs +1 -0
  511. package/src/ui/statusline-agents.mjs +1 -3
  512. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  513. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  514. package/scripts/code-graph-description-contract.mjs +0 -188
  515. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -7,8 +7,19 @@
7
7
  * Only turns the origin agent starts (the brief, a later `send` from it)
8
8
  * report: when such a turn ends its result is queued durably for the agent's
9
9
  * home (agent-home-delivery.mjs), which posts it and wakes the agent once to
10
- * relay it. A turn blocked on the user's answer or approval posts one faint
11
- * line instead; the card stays in the task session for the user.
10
+ * relay it. The session's other answers in that chain (given while work it
11
+ * handed to its agents still runs) are queued as progress updates that wake
12
+ * nobody: the agent reads them before its next turn, whatever starts it
13
+ * (agent-home-delivery.mjs postBeforeTurn), and the final report never
14
+ * repeats one that already carried its answer. A turn blocked on the user's
15
+ * answer or approval posts one faint line instead; the card stays in the task
16
+ * session for the user.
17
+ *
18
+ * Whether a turn reports is apart from whose work it is: every accepted send
19
+ * of an agent is the agent's work in that session (bot-owned-work.mjs, shown
20
+ * as the bot working) until its chain ends, the point a report would be
21
+ * final, and with `notify: false` or an unstored watch it reports nothing.
22
+ * A turn the user types there is owned by no agent.
12
23
  *
13
24
  * An agent's send to any other session reports the same way, by an origin for
14
25
  * that send only (reportOrigin). Every reporting turn is also watched durably
@@ -36,6 +47,7 @@ import { wrapAgentMessage } from '../runtime/shared/agent-message-envelope.mjs';
36
47
  import { hasPendingAgentWork, sessionsWithPendingWork } from '../runtime/shared/pending-agent-work.mjs';
37
48
  import { agentTaskIdOfExecution } from './services/agent-tool/restart-notify.mjs';
38
49
  import { botChainOf } from '../runtime/shared/bot-hops.mjs';
50
+ import { endBotWork, noteBotWork } from '../runtime/shared/bot-owned-work.mjs';
39
51
  import { worktreeError, worktreeNameError } from '../runtime/shared/worktrees/contract.mjs';
40
52
  import { samePath } from '../runtime/shared/worktrees/paths.mjs';
41
53
  import { readModelCatalog, routeByDisplayName, withEffort } from './task-session-route.mjs';
@@ -79,12 +91,15 @@ const turnStatus = (detail) =>
79
91
 
80
92
  const INTERRUPTED = /^\[Request interrupted/;
81
93
 
82
- const messageText = (content) =>
83
- typeof content === 'string'
84
- ? content
85
- : (Array.isArray(content) ? content : [])
86
- .map((part) => (typeof part === 'string' ? part : part?.type === 'text' ? part.text || '' : ''))
87
- .join('');
94
+ const partText = (part) => {
95
+ if (typeof part === 'string') return part;
96
+ return part?.type === 'text' ? part.text || '' : '';
97
+ };
98
+
99
+ const messageText = (content) => {
100
+ if (typeof content === 'string') return content;
101
+ return (Array.isArray(content) ? content : []).map(partText).join('');
102
+ };
88
103
 
89
104
  /** The submissions a stored user message delivered: a turn's prompt
90
105
  * (`checkpointId`, `submissionIds` of a merged batch) or a steered message. */
@@ -106,13 +121,21 @@ export function submissionIdsOf(message) {
106
121
  export function submissionOutcome(messages, runId) {
107
122
  const list = Array.isArray(messages) ? messages : [];
108
123
  const start = list.findIndex((message) => submissionIdsOf(message).includes(runId));
109
- if (start < 0) return null;
124
+ return start < 0 ? null : outcomeFrom(list, start);
125
+ }
126
+
127
+ /** submissionOutcome of the turn whose prompt is `list[start]`, read in
128
+ * place: the scan stops at that turn's end (its completion, an
129
+ * interruption, or the next turn's prompt). */
130
+ function outcomeFrom(list, start) {
110
131
  for (let index = start; index < list.length; index += 1) {
111
132
  const message = list[index];
112
133
  const completion = message?.meta?.transcript?.completion;
113
134
  if (completion) {
114
135
  const status = turnStatus(completion);
115
- const text = status === 'failed' ? completion.detail || '' : message.role === 'assistant' ? message.content : '';
136
+ let text = '';
137
+ if (status === 'failed') text = completion.detail || '';
138
+ else if (message.role === 'assistant') text = message.content;
116
139
  return { status, text: messageText(text) };
117
140
  }
118
141
  if (index === start || message?.role !== 'user') continue;
@@ -142,7 +165,9 @@ const callArguments = (call) => {
142
165
  * no task. Empty when the turn recorded none. */
143
166
  function consumedTaskIds(message) {
144
167
  const recorded = message?.meta?.transcript?.consumedExecutionIds;
145
- const ids = Array.isArray(recorded) ? recorded : message?.meta?.execution?.id ? [message.meta.execution.id] : [];
168
+ let ids = [];
169
+ if (Array.isArray(recorded)) ids = recorded;
170
+ else if (message?.meta?.execution?.id) ids = [message.meta.execution.id];
146
171
  return ids.map((id) => (id ? agentTaskIdOfExecution(id) : null));
147
172
  }
148
173
 
@@ -164,7 +189,9 @@ export function latestTurnOutcome(messages, runId) {
164
189
  return chain && { id: chain.id, outcome: chain.outcome, at: chain.at, ambiguous: chain.ambiguous };
165
190
  }
166
191
 
167
- /** latestTurnOutcome with what a report's wait needs besides: `taskIds`, the
192
+ /** latestTurnOutcome with what a report's wait needs besides: `answers`,
193
+ * every turn of the chain that recorded its end, oldest first (`{ id,
194
+ * status, text }`: its submission id and submissionOutcome); `taskIds`, the
168
195
  * agent tasks the chain's turns started — the only tasks proven the chain's
169
196
  * — and `busyIsOthers`: the session's last stored turn is one outside the
170
197
  * chain that recorded no end yet, and a stored turn took the result of every
@@ -187,6 +214,8 @@ function turnChain(messages, runId) {
187
214
  // The turn the walk is in: where its prompt is stored and its submission id.
188
215
  let turnAt = start;
189
216
  let turnId = runId;
217
+ // Every turn of the chain, in order: where its prompt is stored, its id.
218
+ const turns = [{ at: start, id: runId }];
190
219
  for (let index = start; index < list.length; index += 1) {
191
220
  const message = list[index];
192
221
  const checkpointId = index > start && message?.role === 'user' ? message.meta?.transcript?.checkpointId : '';
@@ -209,6 +238,7 @@ function turnChain(messages, runId) {
209
238
  inChain = true;
210
239
  at = turnAt;
211
240
  id = turnId;
241
+ if (turns.at(-1).id !== turnId) turns.push({ at: turnAt, id: turnId });
212
242
  } else if (!delivered.length || delivered.includes(null)) {
213
243
  ambiguous = true;
214
244
  }
@@ -227,14 +257,27 @@ function turnChain(messages, runId) {
227
257
  if (Array.isArray(message.agentTaskIds)) for (const taskId of message.agentTaskIds) started.add(String(taskId));
228
258
  else if (message.toolKind !== 'error') ambiguous = true;
229
259
  }
230
- const turn = list.slice(at);
231
- const ended = turn.find((message) => message?.meta?.transcript?.completion);
232
- const last = turnId === id ? null : submissionOutcome(list.slice(turnAt), turnId);
260
+ // Read in place, never on copied suffixes: each chain turn's outcome scan
261
+ // stops at that turn's end, so the turns' scans cover disjoint stretches
262
+ // and the whole walk stays linear in the transcript.
263
+ let ended = null;
264
+ for (let index = at; index < list.length && !ended; index += 1) {
265
+ if (list[index]?.meta?.transcript?.completion) ended = list[index];
266
+ }
267
+ const last = turnId === id ? null : outcomeFrom(list, turnAt);
268
+ const answers = [];
269
+ let outcome = null;
270
+ for (const chainTurn of turns) {
271
+ const turnOutcome = outcomeFrom(list, chainTurn.at);
272
+ if (chainTurn.at === at) outcome = turnOutcome;
273
+ if (turnOutcome !== 'open') answers.push({ id: chainTurn.id, ...turnOutcome });
274
+ }
233
275
  return {
234
276
  id,
235
- outcome: submissionOutcome(turn, id),
277
+ outcome,
236
278
  at: Number(ended?.meta?.transcript?.at) || 0,
237
279
  ambiguous,
280
+ answers,
238
281
  taskIds: started,
239
282
  busyIsOthers: last === 'open' && [...started].every((taskId) => taken.has(taskId)),
240
283
  };
@@ -333,7 +376,7 @@ function chainPendingWork(sessionId, taskIds, { owed, pool, agentTaskOwners, wor
333
376
  /** The agent results queued for `sessionId` that no turn has taken yet (its
334
377
  * durable pending queue: an agent's completion, a restart failure): `{ text,
335
378
  * meta }` as the notification bus delivers them. */
336
- export async function owedAgentResults(sessionId) {
379
+ async function owedAgentResults(sessionId) {
337
380
  const [{ _getPendingMessagesForSession }, { completionIsDelivered, isCompletionNotificationEntry }] =
338
381
  await Promise.all([
339
382
  import('../runtime/agent/orchestrator/session/manager/pending-messages.mjs'),
@@ -399,9 +442,6 @@ const LOST_TEXT = 'The message never ran: the app restarted before its turn star
399
442
  const GONE_TEXT = 'The session no longer exists.';
400
443
  const RESUME_FAILED_TEXT =
401
444
  'The agent task session could not continue after the app restarted: its agents’ results never reached it. Check that session.';
402
- const PENDING_CAP_MS = 30 * 60_000;
403
- const STILL_WORKING_TEXT =
404
- 'Still in progress: its agents are still working 30+ minutes after this answer. This is the latest answer; the final one is reported when they finish.';
405
445
  // Deliveries of a restarted task session's queued agent results before its
406
446
  // report gives up as RESUME_FAILED_TEXT.
407
447
  const OWED_WAKE_TRIES = 3;
@@ -472,27 +512,54 @@ async function lastUsedSessionRoute(sessionService) {
472
512
  * without the provenance to tell (no stored turn, `ambiguous`) the whole
473
513
  * session must be idle with none pending. `immediate` is runId's own end,
474
514
  * when this process saw it.
475
- * `finish(status, text)` runs once; `check` re-evaluates (call it on every
476
- * state change of `runtime`), `stop` ends the wait. */
515
+ * Every other answer of the chain — each ended turn a later turn of the
516
+ * chain followed, and the latest while its work still runs or its
517
+ * provenance is missing — goes to `interim({ id, status, text })` once, in
518
+ * chain order (`id`: the submission of the turn that gave it): a progress
519
+ * update, which wakes nobody. Every check lists all the chain's ended turns,
520
+ * so answers that end close together are each reported. `progressedThrough`:
521
+ * the last turn an earlier process reported (the watch's durable mark):
522
+ * it and the turns before it are not reported again.
523
+ * `finish(status, text, answerId)` runs once (`answerId`: the turn whose
524
+ * answer it is, none for a report of no turn); `check` re-evaluates (call it
525
+ * on every state change of `runtime`), `stop` ends the wait. */
477
526
  function followTaskToFinal(
478
- { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
479
- { sessionId, runId, runtime, immediate = null, wakeOwed = false, finish, progress = null }
527
+ { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, log },
528
+ { sessionId, runId, runtime, immediate = null, wakeOwed = false, finish, interim = null, progressedThrough = '' }
480
529
  ) {
481
- let progressed = false;
482
- let pendingSince = 0;
483
- // Agent work still pending `pendingCapMs` after the chain's latest turn
484
- // ended: `progress(text)` reports that answer once, marked still in
485
- // progress, and the wait for the final one goes on. `latest`: the chain's
486
- // latest turn (turnChain).
487
- async function reportProgress(latest) {
488
- if (progressed || typeof progress !== 'function') return;
489
- let end = latest?.outcome && latest.outcome !== 'open' ? latest.outcome : null;
490
- if (immediate && (!latest || latest.id === runId)) end = immediate;
491
- if (!end) return;
492
- pendingSince ||= Date.now();
493
- if (Date.now() - (latest?.at || pendingSince) < pendingCapMs) return;
494
- progressed = true;
495
- await progress(`${STILL_WORKING_TEXT}\n\n${end.text}`);
530
+ // The chain's ended answers, oldest first, `{ id, status, text }`; runId's
531
+ // own is `immediate` when this process saw it. `latest`: turnChain's.
532
+ const answersOf = (latest) => {
533
+ const stored = latest?.answers || [];
534
+ if (!immediate) return stored;
535
+ return [{ id: runId, ...immediate }, ...stored.filter((answer) => answer.id !== runId)];
536
+ };
537
+ // The answers already handed to `interim`, by turn.
538
+ const interimIds = new Set();
539
+ /** Hand `answer` to `interim` once; false when that failed (logged): it is
540
+ * tried again at the next check, and no later answer goes out before it. */
541
+ async function reportInterim(answer) {
542
+ if (!answer?.id || !String(answer.text || '').trim() || typeof interim !== 'function') return true;
543
+ if (interimIds.has(answer.id)) return true;
544
+ try {
545
+ await interim(answer);
546
+ } catch (error) {
547
+ log(`progress update of ${sessionId} failed, retried at the next check: ${error?.message || error}`);
548
+ return false;
549
+ }
550
+ interimIds.add(answer.id);
551
+ return true;
552
+ }
553
+ /** Report, in order, every one of `answers` past the durable mark but
554
+ * `held` (the final candidate, reported only when it proves not final);
555
+ * false, stopping there, at the first that could not be queued. */
556
+ async function reportEarlier(answers, held) {
557
+ const through = progressedThrough ? answers.findIndex((answer) => answer.id === progressedThrough) : -1;
558
+ for (const [index, answer] of answers.entries()) {
559
+ if (index <= through) interimIds.add(answer.id);
560
+ else if (answer !== held && !(await reportInterim(answer))) return false;
561
+ }
562
+ return true;
496
563
  }
497
564
  let done = false;
498
565
  let checking = false;
@@ -553,6 +620,14 @@ function followTaskToFinal(
553
620
  const latest = turnChain(stored?.messages, runId);
554
621
  // The chain's agent tasks; null (the whole session) without its provenance.
555
622
  const taskIds = latest && !latest.ambiguous ? latest.taskIds : null;
623
+ // Every answer of the chain but its latest turn's is no final one: each
624
+ // goes out as progress, whatever the session does now.
625
+ const answers = answersOf(latest);
626
+ const latestId = latest ? latest.id : runId;
627
+ const answer = answers.at(-1)?.id === latestId ? answers.at(-1) : null;
628
+ // An answer that could not be queued holds the chain (later answers, the
629
+ // final report) until a later check queues it: the poll retries it.
630
+ if (!(await reportEarlier(answers, answer))) return null;
556
631
  // A busy session may run the chain's turn unless that turn is provably another's.
557
632
  const busy = Boolean(runtime?.getState?.()?.busy);
558
633
  if (busy && !(taskIds && latest.busyIsOthers)) return null;
@@ -561,18 +636,16 @@ function followTaskToFinal(
561
636
  if (woke !== undefined) return woke;
562
637
  }
563
638
  if (await pendingWork(sessionId, { taskIds })) {
564
- await reportProgress(latest);
639
+ await reportInterim(answer);
565
640
  return null;
566
641
  }
567
642
  if (!stored && !immediate) return { key: 'gone', status: 'cancelled', text: GONE_TEXT };
568
643
  // Provenance the chain needs is missing: its answer may not be final.
569
644
  if (latest?.ambiguous) {
570
- await reportProgress(latest);
645
+ await reportInterim(answer);
571
646
  return null;
572
647
  }
573
- if (immediate && (!latest || latest.id === runId)) return { key: runId, ...immediate };
574
- if (!latest?.outcome || latest.outcome === 'open') return null;
575
- return { key: `${latest.id}:${latest.outcome.status}`, ...latest.outcome };
648
+ return answer && { key: `${answer.id}:${answer.status}`, ...answer };
576
649
  }
577
650
  async function check() {
578
651
  if (done) return;
@@ -598,7 +671,7 @@ function followTaskToFinal(
598
671
  arm(candidateAt + settleMs - Date.now());
599
672
  } else {
600
673
  stop();
601
- await finish(end.status, end.text);
674
+ await finish(end.status, end.text, end.id);
602
675
  }
603
676
  } while (again && !done);
604
677
  } finally {
@@ -747,17 +820,24 @@ async function sendReportOrigin({ loadSession, agentName }, sessionId, agentId,
747
820
  }
748
821
 
749
822
  /** Re-attach one watch a stopped daemon left: report the turn's end once
750
- * from the target's record, or watch it until it ends. */
823
+ * from the target's record, or watch it until it ends. `quiet`: `watch` is
824
+ * the record of work that reports nothing (no home, no title): the same
825
+ * follow, but its end is only the end of the agent's work, dropped from
826
+ * `disown`, and no progress update or report goes out. */
751
827
  async function rearmTaskWatch(
752
- { deliver, loadSession, materializeSession, queuedSubmissionIds, artifactFields, followToFinal, log },
753
- watch
828
+ { deliver, disown, loadSession, materializeSession, queuedSubmissionIds, artifactFields, followToFinal, log },
829
+ watch,
830
+ { quiet = false } = {}
754
831
  ) {
755
832
  const sessionId = watch.taskSessionId;
756
833
  // The turn that answers: the watched submission's own, or the one it
757
834
  // follows (a room member's turn under another submission id).
758
835
  const turnId = watch.follows || watch.runId;
759
- const finish = async (status, text = '') =>
760
- deliver(watch.agentId, {
836
+ // The work is the agent's until its end is reported (or cannot be).
837
+ const finish = async (status, text = '', answerId = '') => {
838
+ endBotWork(watch.runId);
839
+ if (quiet) return disown(watch.runId);
840
+ return deliver(watch.agentId, {
761
841
  runId: `${watch.runId}_end`,
762
842
  taskSessionId: sessionId,
763
843
  title: watch.title,
@@ -767,8 +847,10 @@ async function rearmTaskWatch(
767
847
  ...(watch.hop > 0 ? { hop: watch.hop } : {}),
768
848
  ...roomFrom(watch.homeSessionId),
769
849
  watchId: watch.runId,
850
+ ...progressOf(watch.runId, answerId),
770
851
  ...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT ? {} : await artifactFields(sessionId, turnId)),
771
852
  });
853
+ };
772
854
  if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
773
855
  let runtime;
774
856
  try {
@@ -778,6 +860,7 @@ async function rearmTaskWatch(
778
860
  log(`${sessionId} could not be loaded after the restart: ${error?.message || error}`);
779
861
  return finish('failed', RESUME_FAILED_TEXT);
780
862
  }
863
+ noteBotWork({ runId: watch.runId, sessionId, agentId: watch.agentId });
781
864
  let done = false;
782
865
  let checking = false;
783
866
  let again = false;
@@ -811,22 +894,30 @@ async function rearmTaskWatch(
811
894
  sessionId,
812
895
  runId: turnId,
813
896
  runtime,
814
- wakeOwed: true,
815
- progress: (text) =>
816
- deliver(watch.agentId, {
817
- runId: `${watch.runId}_progress`,
818
- taskSessionId: sessionId,
819
- title: watch.title,
820
- status: 'working',
821
- text,
822
- ...(watch.target === 'session' ? { target: 'session' } : {}),
823
- ...(watch.hop > 0 ? { hop: watch.hop } : {}),
824
- ...roomFrom(watch.homeSessionId),
825
- }),
826
- finish: async (status, text) => {
897
+ // Only a report wakes the session for the results the restart left
898
+ // it; work that reports nothing is only observed, never driven.
899
+ wakeOwed: !quiet,
900
+ // The turns an earlier process already reported as progress.
901
+ progressedThrough: watch.progressed || '',
902
+ interim: quiet
903
+ ? null
904
+ : (answer) =>
905
+ deliver(watch.agentId, {
906
+ runId: progressRunId(watch.runId, answer.id),
907
+ taskSessionId: sessionId,
908
+ title: watch.title,
909
+ status: 'working',
910
+ text: answer.text,
911
+ progressWatchId: watch.runId,
912
+ answerId: answer.id,
913
+ ...(watch.target === 'session' ? { target: 'session' } : {}),
914
+ ...(watch.hop > 0 ? { hop: watch.hop } : {}),
915
+ ...roomFrom(watch.homeSessionId),
916
+ }),
917
+ finish: async (status, text, answerId) => {
827
918
  done = true;
828
919
  unsubscribe();
829
- await finish(status, text);
920
+ await finish(status, text, answerId);
830
921
  },
831
922
  });
832
923
  } else if (end) {
@@ -845,16 +936,180 @@ async function rearmTaskWatch(
845
936
  await check();
846
937
  }
847
938
 
939
+ /** The settlement of sends one turn answers: they settle together with the
940
+ * same `detail` object (a send steered into the turn while it ran, or queued
941
+ * and taken as one batch), and that turn reports once, closing every one of
942
+ * them. `nextSeq()` numbers the sends in submission order: the earliest of
943
+ * the sends one turn answers reports for them. */
944
+ function createSendSettlement(log) {
945
+ const settledTurns = new WeakMap();
946
+ let sendCount = 0;
947
+
948
+ /** Add send `member` (`{ seq, id, notify, unwatch, settle }`) to the settlement
949
+ * `detail` of its turn, per reporting conversation. The sends of one turn
950
+ * settle in the same tick; after it the earliest (the turn's own, whose
951
+ * record holds the whole turn) settles with all their ids and the others
952
+ * stand down. */
953
+ function join(detail, origin, member) {
954
+ // Sends that report and sends that do not settle apart: the group's first
955
+ // member settles for the group, so a quiet one must never stand for a
956
+ // send that owes a report.
957
+ const key = `${origin.agentId}\0${origin.sessionId}\0${member.notify ? 'report' : 'quiet'}`;
958
+ let groups = settledTurns.get(detail);
959
+ if (!groups) {
960
+ groups = new Map();
961
+ settledTurns.set(detail, groups);
962
+ }
963
+ let members = groups.get(key);
964
+ if (!members) {
965
+ members = [];
966
+ groups.set(key, members);
967
+ const settling = members;
968
+ void Promise.resolve()
969
+ .then(() => {
970
+ const [first, ...others] = settling.sort((a, b) => a.seq - b.seq);
971
+ for (const other of others) other.unwatch();
972
+ first.settle(settling.map((entry) => entry.id));
973
+ })
974
+ .catch((error) => log(`settling the sends of one turn failed: ${error?.message || error}`));
975
+ }
976
+ members.push(member);
977
+ }
978
+
979
+ const nextSeq = () => {
980
+ sendCount += 1;
981
+ return sendCount;
982
+ };
983
+
984
+ return { join, nextSeq };
985
+ }
986
+
987
+ /** The queue row of send `id`'s progress update carrying the answer of turn
988
+ * `answerId` (followToFinal `interim`): one per answer, across restarts. */
989
+ const progressRunId = (id, answerId) => `${id}_progress_${answerId}`;
990
+
991
+ /** The queue row of send `id`'s report of `status`. 'working': a progress
992
+ * update (progressRunId), not the end. A wait for approval belongs to the
993
+ * target session, not to this send: every watcher of it names the same row,
994
+ * so it posts once. */
995
+ const turnReportRunId = (sessionId, id, status, { approvalId = '', answerId = '' } = {}) => {
996
+ if (status === 'blocked') return `${sessionId}_${approvalId}_blocked`;
997
+ return status === 'working' ? progressRunId(id, answerId) : `${id}_end`;
998
+ };
999
+
1000
+ /** The report fields naming the progress update that may have carried the
1001
+ * final answer of turn `answerId` already (the queue then does not repeat
1002
+ * it); none for a report of no turn. */
1003
+ const progressOf = (id, answerId) => (answerId ? { progressRunId: progressRunId(id, answerId) } : {});
1004
+
1005
+ /** The handler of send `id`'s task-session state: a wait for the user's
1006
+ * approval reports blocked once; a later, different one reports again. */
1007
+ function approvalWaitReporter(id, report) {
1008
+ // The approval wait last reported.
1009
+ let reportedApproval = null;
1010
+ return (state) => {
1011
+ if (!state.toolApproval) {
1012
+ reportedApproval = null;
1013
+ return;
1014
+ }
1015
+ const approvalId = String(state.toolApproval.id ?? 'approval');
1016
+ if (approvalId === reportedApproval) return;
1017
+ // A send still queued behind the running turn is not the turn
1018
+ // waiting: that turn's own watcher reports it.
1019
+ if ((state.queued || []).some((entry) => entry?.id === id)) return;
1020
+ reportedApproval = approvalId;
1021
+ void report('blocked', '', { approvalId });
1022
+ };
1023
+ }
1024
+
1025
+ /** The transcript meta of a send from `origin`. `fromAgent`: the sending bot,
1026
+ * recorded when it writes into another bot's home chat (otherwise the row
1027
+ * would read as the user's); `hop`: its place in a bot-to-bot chain
1028
+ * (bot-hops.mjs). */
1029
+ const sendTranscriptMeta = (origin) => ({
1030
+ sender: 'lead',
1031
+ ...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
1032
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1033
+ // The bots that handed this work on, the sender last (bot-hops.mjs).
1034
+ ...(origin.botChain?.length ? { botChain: origin.botChain } : {}),
1035
+ });
1036
+
1037
+ /** What a send tells its caller once submitted. */
1038
+ const sendResult = (accepted, notify, id, noReport) => ({
1039
+ accepted,
1040
+ // Names this send; its terminal report carries it back (routine.sendId).
1041
+ ...(accepted && notify ? { sendId: id } : {}),
1042
+ reportsBack: accepted && notify,
1043
+ ...(accepted && !notify ? { reportsBackReason: noReport } : {}),
1044
+ });
1045
+
1046
+ /** create's brief and `worktree` location, checked before anything runs. */
1047
+ function createRequest(args, callerAgentId, callerScope) {
1048
+ if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
1049
+ const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
1050
+ if (!brief) throw new Error('session: brief is required for action=create');
1051
+ const location = taskLocation(args.worktree);
1052
+ if (location.mode === 'worktree') assertWorktreeMutationAllowed(callerScope, 'session: create with a worktree');
1053
+ return { brief, location };
1054
+ }
1055
+
1056
+ /** Where create starts (resolveTaskProject). An explicit worktree is never
1057
+ * silently dropped: it needs a registered Project. */
1058
+ function createWhere(args, projects, callerCwd, location) {
1059
+ const where = resolveTaskProject(args.project, { projects, cwd: callerCwd || process.cwd() });
1060
+ const registered = where.project && projects.some((entry) => samePath(entry.path, where.project.path));
1061
+ if (location.mode === 'worktree' && !registered) {
1062
+ throw taskWorktreeFailure(
1063
+ worktreeError('WORKTREE_PROJECT_UNKNOWN', 'A worktree needs a registered Project; run list-projects.', {
1064
+ projectPath: where.project?.path ?? null,
1065
+ })
1066
+ );
1067
+ }
1068
+ return where;
1069
+ }
1070
+
1071
+ /** The worktree failure `error` of task session `sessionId` whose empty
1072
+ * session could not be dropped (`dropFailure`): the model must know the
1073
+ * empty session is still there to delete. */
1074
+ const orphanSessionError = (error, sessionId, dropFailure) =>
1075
+ Object.assign(
1076
+ new Error(
1077
+ `${error?.message || error} The empty task session ${sessionId} could not be deleted either (${dropFailure?.message || dropFailure}); delete it with session action=delete session_id=${sessionId}.`
1078
+ ),
1079
+ {
1080
+ ...(typeof error?.code === 'string' ? { code: error.code } : {}),
1081
+ ...(error?.details ? { details: error.details } : {}),
1082
+ orphanSessionId: sessionId,
1083
+ cause: error,
1084
+ }
1085
+ );
1086
+
1087
+ /** `ready` resolves once `started()` runs: "Started" lands in the home before
1088
+ * any report of the brief's turn. */
1089
+ function startedGate() {
1090
+ let started = () => {};
1091
+ const ready = new Promise((resolveReady) => {
1092
+ started = resolveReady;
1093
+ });
1094
+ return { ready, started };
1095
+ }
1096
+
848
1097
  export function createAgentTaskSessions({
849
1098
  getSessionService,
850
1099
  deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
851
- // The durable watch of each reporting turn until its end is queued.
1100
+ // The durable watch of each reporting turn until its end is queued. A bot's
1101
+ // turn that reports nothing (reports off, or a watch that could not be
1102
+ // stored) keeps a record of its work instead (`own`, `owned`, `disown`),
1103
+ // which no report and no hand-off follows: a restart re-attaches both.
852
1104
  watches = {
853
1105
  add: async (watch) => (await import('./agent-home-delivery.mjs')).watchAgentTask(watch),
854
1106
  list: async () => (await import('./agent-home-delivery.mjs')).agentTaskWatches(),
855
1107
  remove: async (runId) => (await import('./agent-home-delivery.mjs')).unwatchAgentTask(runId),
856
1108
  follow: async (runId, submissionId) =>
857
1109
  (await import('./agent-home-delivery.mjs')).followAgentTask(runId, submissionId),
1110
+ own: async (work) => (await import('./agent-home-delivery.mjs')).ownAgentTask(work),
1111
+ owned: async () => (await import('./agent-home-delivery.mjs')).agentOwnedTasks(),
1112
+ disown: async (runId) => (await import('./agent-home-delivery.mjs')).disownAgentTask(runId),
858
1113
  },
859
1114
  // Submissions still queued (durably) for a session after a restart.
860
1115
  queuedSubmissionIds = async (sessionId) =>
@@ -868,7 +1123,7 @@ export function createAgentTaskSessions({
868
1123
  (await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).worktree?.newTaskDefault,
869
1124
  modelRoute = configModelRoute,
870
1125
  // The model menu's catalog rows, read through the calling session's runtime.
871
- modelCatalog = (sessionId) => readModelCatalog(service(), sessionId),
1126
+ modelCatalog = (sessionId) => readModelCatalog(getSessionService(), sessionId),
872
1127
  // The Lead (Main) route of the config — what a desktop New task starts on.
873
1128
  leadRoute = configLeadRoute,
874
1129
  // The model the user last used in a coding session.
@@ -899,9 +1154,6 @@ export function createAgentTaskSessions({
899
1154
  owedResults = owedAgentResults,
900
1155
  deliverCompletion = null,
901
1156
  settleAgentResults = async () => {},
902
- // Pending agent work this long after the latest answer reports that answer
903
- // as still in progress (once); the final report still follows.
904
- pendingCapMs = PENDING_CAP_MS,
905
1157
  log = (line) => process.stderr.write(`mixdog task sessions: ${line}\n`),
906
1158
  }) {
907
1159
  /** The report fields of turn `runId`'s files; none when it produced none. */
@@ -923,44 +1175,9 @@ export function createAgentTaskSessions({
923
1175
  };
924
1176
 
925
1177
  const followToFinal = (follow) =>
926
- followTaskToFinal(
927
- { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
928
- follow
929
- );
1178
+ followTaskToFinal({ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, log }, follow);
930
1179
 
931
- // Sends one turn answers settle together with the same `detail` object: a
932
- // send steered into the turn while it ran, or queued and taken as one batch.
933
- // That turn reports once, closing every one of them.
934
- const settledTurns = new WeakMap();
935
- let sendCount = 0;
936
-
937
- /** Add send `member` (`{ seq, id, unwatch, settle }`) to the settlement
938
- * `detail` of its turn, per reporting conversation. The sends of one turn
939
- * settle in the same tick; after it the earliest (the turn's own, whose
940
- * record holds the whole turn) settles with all their ids and the others
941
- * stand down. */
942
- function joinSettlement(detail, origin, member) {
943
- const key = `${origin.agentId}\0${origin.sessionId}`;
944
- let groups = settledTurns.get(detail);
945
- if (!groups) {
946
- groups = new Map();
947
- settledTurns.set(detail, groups);
948
- }
949
- let members = groups.get(key);
950
- if (!members) {
951
- members = [];
952
- groups.set(key, members);
953
- const settling = members;
954
- void Promise.resolve()
955
- .then(() => {
956
- const [first, ...others] = settling.sort((a, b) => a.seq - b.seq);
957
- for (const other of others) other.unwatch();
958
- first.settle(settling.map((entry) => entry.id));
959
- })
960
- .catch((error) => log(`settling the sends of one turn failed: ${error?.message || error}`));
961
- }
962
- members.push(member);
963
- }
1180
+ const settlement = createSendSettlement(log);
964
1181
 
965
1182
  /** Submit `prompt` as the origin agent's turn; its end (and a wait for the
966
1183
  * user in it) reports to the agent's home. Resolves on intake. `submit`
@@ -982,50 +1199,87 @@ export function createAgentTaskSessions({
982
1199
  watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
983
1200
  let runtime = null;
984
1201
  // The sends this turn's end closes: this one, plus any that its turn
985
- // answered too (joinSettlement).
1202
+ // answered too (createSendSettlement).
986
1203
  let sendIds = [id];
987
1204
  // A report that cannot be queued is retried in this process; when every try
988
1205
  // fails the durable watch stays and restart recovery delivers it (the queue
989
1206
  // dedupes by runId, so a retry never posts twice).
990
- const report = (status, text = '') =>
991
- Promise.resolve(beforeReport)
992
- .then(async () =>
993
- deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
994
- // 'working': a still-in-progress answer (followToFinal), not the end.
995
- runId: `${id}_${{ blocked: 'blocked', working: 'progress' }[status] || 'end'}`,
996
- taskSessionId: sessionId,
997
- title: origin.title,
998
- status,
999
- text,
1000
- ...target,
1001
- ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1002
- ...roomFrom(origin.sessionId),
1003
- // A turn's end brings back the files it produced, when the agent
1004
- // may receive them (reportOrigin `textOnly`).
1005
- ...(status === 'blocked' || status === 'working'
1006
- ? {}
1007
- : {
1008
- watchId: id,
1009
- ...(sendIds.length > 1 ? { watchIds: sendIds } : {}),
1010
- ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)),
1011
- }),
1012
- })
1013
- )
1014
- .catch((error) => log(`report of ${sessionId} failed: ${error?.message || error}`));
1207
+ // `answerId`: the turn whose answer a progress update or the end carries.
1208
+ // `deliverReport` rejects once every try failed (a progress update is
1209
+ // tried again at the follow's next check); `report` logs it instead.
1210
+ const deliverReport = (status, text = '', { approvalId = '', answerId = '' } = {}) =>
1211
+ Promise.resolve(beforeReport).then(async () =>
1212
+ deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
1213
+ runId: turnReportRunId(sessionId, id, status, { approvalId, answerId }),
1214
+ taskSessionId: sessionId,
1215
+ title: origin.title,
1216
+ status,
1217
+ text,
1218
+ ...target,
1219
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1220
+ ...roomFrom(origin.sessionId),
1221
+ // A progress update moves this send's durable mark to its turn
1222
+ // (agent-home-delivery.mjs enqueueTask), in the same write.
1223
+ ...(status === 'working' ? { progressWatchId: id, answerId } : {}),
1224
+ // A turn's end brings back the files it produced, when the agent
1225
+ // may receive them (reportOrigin `textOnly`).
1226
+ ...(status === 'blocked' || status === 'working'
1227
+ ? {}
1228
+ : {
1229
+ watchId: id,
1230
+ ...(sendIds.length > 1 ? { watchIds: sendIds } : {}),
1231
+ ...progressOf(id, answerId),
1232
+ ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)),
1233
+ }),
1234
+ })
1235
+ );
1236
+ const report = (status, text = '', options = {}) =>
1237
+ deliverReport(status, text, options).catch((error) =>
1238
+ log(`report of ${sessionId} failed: ${error?.message || error}`)
1239
+ );
1015
1240
  let unwatch = () => {};
1016
- // Set once the turn ended completed: the report waits for the final answer.
1241
+ // The agent's work in `sessionId`: owned from the send's acceptance until
1242
+ // it settles or is cancelled, whether or not it reports (`notify`). A send
1243
+ // that holds no report watch keeps a durable record of the work instead
1244
+ // (`watches.own`), so a restart re-attaches it (recover); the writes of
1245
+ // one send go in order, so a record never outlives its work.
1246
+ let owning = true;
1247
+ let recorded = false;
1248
+ let recording = Promise.resolve();
1249
+ const record = (write) => {
1250
+ recording = recording
1251
+ .then(write)
1252
+ .catch((error) => log(`recording the work of ${sessionId} failed: ${error?.message || error}`));
1253
+ return recording;
1254
+ };
1255
+ // The work of every send `ids` this turn answered ends together: one
1256
+ // write drops all their records (a send standing down for the turn's
1257
+ // first leaves its record to that one write).
1258
+ const disown = (ids = [id]) => {
1259
+ owning = false;
1260
+ for (const done of ids) endBotWork(done);
1261
+ if (recorded || ids.length > 1) void record(() => watches.disown?.(ids));
1262
+ };
1263
+ const standDown = () => {
1264
+ unwatch();
1265
+ owning = false;
1266
+ endBotWork(id);
1267
+ };
1268
+ // Set once the turn ended completed: the work and any report wait for the
1269
+ // final answer.
1017
1270
  let follow = null;
1018
1271
  let result;
1019
- /** This send ends the turn `detail` for the sends `answered` (joinSettlement). */
1272
+ /** This send ends the turn `detail` for the sends `answered` (createSendSettlement). */
1020
1273
  const settleTurn = (detail, answered) => {
1021
1274
  sendIds = answered;
1022
1275
  const status = turnStatus(detail);
1023
1276
  const text = detail?.result?.content || detail?.error || '';
1024
- // A failed or cancelled turn reports at once; a completed one
1025
- // reports the task's final answer (followToFinal).
1277
+ // A failed or cancelled turn ends at once; a completed one ends with
1278
+ // the task's final answer (followToFinal).
1026
1279
  if (status !== 'completed') {
1027
1280
  unwatch();
1028
- void report(status, text);
1281
+ disown(sendIds);
1282
+ if (notify) void report(status, text);
1029
1283
  return;
1030
1284
  }
1031
1285
  follow = followToFinal({
@@ -1033,87 +1287,85 @@ export function createAgentTaskSessions({
1033
1287
  runId: id,
1034
1288
  runtime,
1035
1289
  immediate: { status, text },
1036
- finish: async (endStatus, endText) => {
1290
+ finish: async (endStatus, endText, answerId) => {
1037
1291
  unwatch();
1038
- await report(endStatus, endText);
1292
+ disown(sendIds);
1293
+ if (notify) await report(endStatus, endText, { answerId });
1039
1294
  },
1040
- progress: (progressText) => report('working', progressText),
1295
+ // Each other answer of its chain: a progress update that wakes nobody
1296
+ // (only a send that reports gives them).
1297
+ ...(notify ? { interim: (answer) => deliverReport('working', answer.text, { answerId: answer.id }) } : {}),
1041
1298
  });
1042
1299
  follow.check();
1043
1300
  };
1044
1301
  try {
1045
- if (notify) {
1046
- runtime = await sessions.materializeSession(sessionId);
1047
- let blocked = false;
1048
- unwatch =
1049
- runtime?.subscribe?.(() => {
1050
- follow?.check();
1051
- if (blocked || !runtime.getState?.()?.toolApproval) return;
1052
- blocked = true;
1053
- void report('blocked');
1054
- }) || unwatch;
1055
- }
1302
+ runtime = await sessions.materializeSession(sessionId);
1303
+ const onState = notify ? approvalWaitReporter(id, report) : null;
1304
+ unwatch =
1305
+ runtime?.subscribe?.(() => {
1306
+ follow?.check();
1307
+ onState?.(runtime.getState?.() || {});
1308
+ }) || unwatch;
1056
1309
  // A bot's send to a session that is not its own task session reaches the
1057
1310
  // model inside the sender envelope (`envelopeName`, set by reportOrigin).
1058
1311
  const sent = origin.envelopeName
1059
1312
  ? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
1060
1313
  : prompt;
1061
1314
  const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
1062
- // Submission order: the earliest of the sends one turn answers reports for them.
1063
- sendCount += 1;
1064
- const seq = sendCount;
1315
+ const seq = settlement.nextSeq();
1065
1316
  result = await take({
1066
1317
  id,
1067
1318
  mode: 'prompt',
1068
- // `fromAgent`: the sending bot, recorded when it writes into another
1069
- // bot's home chat (otherwise the row would read as the user's);
1070
- // `hop`: its place in a bot-to-bot chain (bot-hops.mjs).
1071
- transcriptMeta: {
1072
- sender: 'lead',
1073
- ...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
1074
- ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1075
- // The bots that handed this work on, the sender last (bot-hops.mjs).
1076
- ...(origin.botChain?.length ? { botChain: origin.botChain } : {}),
1077
- },
1078
- ...(notify
1079
- ? {
1080
- // A room answers this send in a turn under another submission
1081
- // id (one turn for several sends, or a fresh turn for one cut
1082
- // short): the watch follows that turn, recorded before it runs.
1083
- follow: (submissionId) => watches.follow?.(id, submissionId),
1084
- onSettled: (detail) =>
1085
- joinSettlement(detail, origin, {
1086
- seq,
1087
- id,
1088
- unwatch: () => unwatch(),
1089
- settle: (answered) => settleTurn(detail, answered),
1090
- }),
1091
- }
1092
- : {}),
1319
+ transcriptMeta: sendTranscriptMeta(origin),
1320
+ // A room answers this send in a turn under another submission id (one
1321
+ // turn for several sends, or a fresh turn for one cut short): the
1322
+ // watch follows that turn, recorded before it runs.
1323
+ ...(notify ? { follow: (submissionId) => watches.follow?.(id, submissionId) } : {}),
1324
+ onSettled: (detail) =>
1325
+ settlement.join(detail, origin, {
1326
+ seq,
1327
+ id,
1328
+ notify,
1329
+ // Standing down for the turn's first send, which owns the same work.
1330
+ unwatch: standDown,
1331
+ settle: (answered) => settleTurn(detail, answered),
1332
+ }),
1093
1333
  });
1094
1334
  } catch (error) {
1095
1335
  unwatch();
1336
+ disown();
1096
1337
  if (notify) await dropWatch();
1097
1338
  throw error;
1098
1339
  }
1099
1340
  const accepted = result?.accepted === true;
1100
1341
  if (!accepted) {
1101
1342
  unwatch();
1343
+ disown();
1102
1344
  if (notify) await dropWatch();
1345
+ } else if (owning) {
1346
+ // Unless it already settled: nothing then is left to end the work.
1347
+ noteBotWork({ runId: id, sessionId, agentId: origin.agentId });
1348
+ if (!notify) {
1349
+ recorded = true;
1350
+ await record(() =>
1351
+ watches.own?.({
1352
+ runId: id,
1353
+ agentId: origin.agentId,
1354
+ taskSessionId: sessionId,
1355
+ owner: processToken,
1356
+ at: Date.now(),
1357
+ })
1358
+ );
1359
+ }
1103
1360
  }
1104
- return {
1105
- accepted,
1106
- // Names this send; its terminal report carries it back (routine.sendId).
1107
- ...(accepted && notify ? { sendId: id } : {}),
1108
- reportsBack: accepted && notify,
1109
- ...(accepted && !notify ? { reportsBackReason: noReport } : {}),
1110
- };
1361
+ return sendResult(accepted, notify, id, noReport);
1111
1362
  }
1112
1363
 
1113
- const rearm = (watch) =>
1364
+ const rearm = (watch, options) =>
1114
1365
  rearmTaskWatch(
1115
1366
  {
1116
1367
  deliver,
1368
+ disown: async (runId) => watches.disown?.(runId),
1117
1369
  loadSession,
1118
1370
  materializeSession: (sessionId) => service().materializeSession(sessionId),
1119
1371
  queuedSubmissionIds,
@@ -1121,13 +1373,25 @@ export function createAgentTaskSessions({
1121
1373
  followToFinal,
1122
1374
  log,
1123
1375
  },
1124
- watch
1376
+ watch,
1377
+ options
1125
1378
  );
1126
1379
 
1127
- /** Daemon start: re-attach every reporting turn an earlier process watched.
1380
+ /** Daemon start: re-attach every reporting turn an earlier process watched,
1381
+ * and every turn of a bot's that reported nothing but whose work it owned
1382
+ * (nothing is reported for those, they are only the bot's work again).
1128
1383
  * Resolves once each is reported or watched. */
1129
1384
  async function recover() {
1130
- const pending = (await watches.list()).filter((watch) => watch?.owner !== processToken);
1385
+ const earlier = (list) => list.filter((entry) => entry?.owner !== processToken);
1386
+ const pending = earlier(await watches.list());
1387
+ const quiet = earlier(
1388
+ await Promise.resolve(watches.owned?.() ?? [])
1389
+ .then((list) => (Array.isArray(list) ? list : []))
1390
+ .catch((error) => {
1391
+ log(`reading the records of bot work failed: ${error?.message || error}`);
1392
+ return [];
1393
+ })
1394
+ );
1131
1395
  // The agents the restart killed become queued failure results first, so
1132
1396
  // each re-attached task session has them to take (followToFinal wakeOwed).
1133
1397
  if (pending.length) {
@@ -1135,12 +1399,15 @@ export function createAgentTaskSessions({
1135
1399
  .then(settleAgentResults)
1136
1400
  .catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
1137
1401
  }
1138
- await Promise.all(
1139
- pending.map((watch) =>
1140
- rearm(watch).catch((error) => log(`re-attaching ${watch?.taskSessionId} failed: ${error?.message || error}`))
1141
- )
1142
- );
1143
- return pending.length;
1402
+ const reattach = (work, options) =>
1403
+ rearm(work, options).catch((error) =>
1404
+ log(`re-attaching ${work?.taskSessionId} failed: ${error?.message || error}`)
1405
+ );
1406
+ await Promise.all([
1407
+ ...pending.map((watch) => reattach(watch)),
1408
+ ...quiet.map((work) => reattach(work, { quiet: true })),
1409
+ ]);
1410
+ return pending.length + quiet.length;
1144
1411
  }
1145
1412
 
1146
1413
  /** Create the task session for `brief` and start it; returns at once.
@@ -1160,22 +1427,8 @@ export function createAgentTaskSessions({
1160
1427
  attachmentParts = [],
1161
1428
  } = {}
1162
1429
  ) {
1163
- if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
1164
- const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
1165
- if (!brief) throw new Error('session: brief is required for action=create');
1166
- const location = taskLocation(args.worktree);
1167
- if (location.mode === 'worktree') assertWorktreeMutationAllowed(callerScope, 'session: create with a worktree');
1168
- const projects = await listProjects();
1169
- const where = resolveTaskProject(args.project, { projects, cwd: callerCwd || process.cwd() });
1170
- // An explicit worktree is never silently dropped: it needs a registered Project.
1171
- const registered = where.project && projects.some((entry) => samePath(entry.path, where.project.path));
1172
- if (location.mode === 'worktree' && !registered) {
1173
- throw taskWorktreeFailure(
1174
- worktreeError('WORKTREE_PROJECT_UNKNOWN', 'A worktree needs a registered Project; run list-projects.', {
1175
- projectPath: where.project?.path ?? null,
1176
- })
1177
- );
1178
- }
1430
+ const { brief, location } = createRequest(args, callerAgentId, callerScope);
1431
+ const where = createWhere(args, await listProjects(), callerCwd, location);
1179
1432
  const requestId = `atask_${randomUUID()}`;
1180
1433
  const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
1181
1434
  const taskOrigin = {
@@ -1243,25 +1496,10 @@ export function createAgentTaskSessions({
1243
1496
  }
1244
1497
  if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
1245
1498
  if (!dropFailure) throw error;
1246
- // The model must know the empty session is still there to delete.
1247
- throw Object.assign(
1248
- new Error(
1249
- `${error?.message || error} The empty task session ${sessionId} could not be deleted either (${dropFailure?.message || dropFailure}); delete it with session action=delete session_id=${sessionId}.`
1250
- ),
1251
- {
1252
- ...(typeof error?.code === 'string' ? { code: error.code } : {}),
1253
- ...(error?.details ? { details: error.details } : {}),
1254
- orphanSessionId: sessionId,
1255
- cause: error,
1256
- }
1257
- );
1499
+ throw orphanSessionError(error, sessionId, dropFailure);
1258
1500
  }
1259
1501
  }
1260
- // "Started" lands in the home before any report of the brief's turn.
1261
- let started = () => {};
1262
- const ready = new Promise((resolveReady) => {
1263
- started = resolveReady;
1264
- });
1502
+ const { ready, started } = startedGate();
1265
1503
  // The brief carries the files the agent forwarded (workspace-tools.mjs).
1266
1504
  const prompt = handoffContent(brief, attachmentParts);
1267
1505
  const botChain = botChainOf(callerBotChain);