mixdog 1.3.5 → 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 (341) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +2 -11
  4. package/scripts/daemon-stub.mjs +1 -1
  5. package/scripts/gate-local.mjs +4 -2
  6. package/scripts/native-release-status.mjs +2 -2
  7. package/scripts/recall-history-cases.json +1 -1
  8. package/scripts/suite-health.mjs +10 -1
  9. package/scripts/test.mjs +7 -11
  10. package/src/agents/reviewer/AGENT.md +7 -11
  11. package/src/agents/reviewer/agent.json +1 -1
  12. package/src/defaults/shipped-history.json +17 -1
  13. package/src/defaults/skills/browser-use/SKILL.md +59 -11
  14. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  15. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  16. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  17. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  18. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  19. package/src/defaults/skills/setup/SKILL.md +22 -0
  20. package/src/defaults/skills/setup/references/actions.md +39 -5
  21. package/src/defaults/skills/setup/references/surfaces.md +11 -2
  22. package/src/defaults/skills/visualize/SKILL.md +2 -1
  23. package/src/lib/rules-builder.cjs +10 -8
  24. package/src/rules/agent/AGENT.md +6 -6
  25. package/src/rules/lead/LEAD.md +18 -20
  26. package/src/rules/shared/05-tool-calls.md +5 -6
  27. package/src/rules/shared/10-tool-workflow.md +2 -0
  28. package/src/rules/shared/60-verification.md +7 -5
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  30. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  31. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  32. package/src/runtime/agent/orchestrator/config.mjs +17 -1
  33. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  35. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  36. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
  39. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
  40. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
  41. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  42. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  45. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
  46. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  47. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  48. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  49. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
  50. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  52. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
  53. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  54. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  55. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  56. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  57. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  58. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  64. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  65. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  68. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  69. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  71. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  72. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  73. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  76. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  77. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
  78. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  79. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
  82. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
  83. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  84. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  86. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
  88. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  90. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
  91. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  92. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
  93. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  94. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  97. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
  98. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  99. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  101. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  102. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  103. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  104. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  105. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  106. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  107. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  109. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  110. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  112. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  113. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  114. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  115. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  116. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  117. package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
  118. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  120. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  121. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  122. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  123. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  124. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  125. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  126. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  127. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
  128. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  129. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  130. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  131. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  132. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  133. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  134. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  135. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
  136. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  137. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  138. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  141. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  142. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  143. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  144. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  145. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  146. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  147. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  148. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  149. package/src/runtime/browser-bridge/client.mjs +73 -8
  150. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  151. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  152. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  153. package/src/runtime/channels/lib/config.mjs +6 -61
  154. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  155. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  156. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  157. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  158. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  159. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  160. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  161. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  162. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  163. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  164. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  165. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  166. package/src/runtime/channels/lib/scheduler.mjs +33 -147
  167. package/src/runtime/channels/lib/schedules-db.mjs +34 -11
  168. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  169. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
  170. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  171. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  172. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  173. package/src/runtime/channels/lib/worker-main.mjs +10 -34
  174. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  175. package/src/runtime/computer-bridge/actions.mjs +9 -1
  176. package/src/runtime/computer-bridge/client.mjs +180 -8
  177. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  178. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  179. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  180. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  181. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  182. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  183. package/src/runtime/connectors/google-mcp.mjs +10 -12
  184. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  185. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  186. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  187. package/src/runtime/session-search/session-search-db.mjs +73 -9
  188. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  189. package/src/runtime/shared/abort-race.mjs +89 -0
  190. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  191. package/src/runtime/shared/agent-kind.mjs +4 -3
  192. package/src/runtime/shared/bounded-download.mjs +8 -3
  193. package/src/runtime/shared/config.mjs +2 -13
  194. package/src/runtime/shared/connector-accounts.mjs +20 -10
  195. package/src/runtime/shared/connector-catalog.d.mts +29 -0
  196. package/src/runtime/shared/connector-catalog.mjs +150 -0
  197. package/src/runtime/shared/connector-triggers.mjs +22 -19
  198. package/src/runtime/shared/defaults-separation.mjs +2 -14
  199. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  200. package/src/runtime/shared/err-text.mjs +24 -15
  201. package/src/runtime/shared/file-permissions.d.mts +0 -8
  202. package/src/runtime/shared/file-permissions.mjs +0 -11
  203. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  204. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  205. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  206. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  207. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  208. package/src/runtime/shared/mixy-presence.mjs +22 -0
  209. package/src/runtime/shared/orchestration.d.mts +2 -0
  210. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  211. package/src/runtime/shared/profile-config.cjs +1 -1
  212. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  213. package/src/runtime/shared/prompt-origin.mjs +21 -0
  214. package/src/runtime/shared/provider-accounts.mjs +7 -1
  215. package/src/runtime/shared/provider-display-name.mjs +29 -0
  216. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  217. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  218. package/src/runtime/shared/routine-trigger.mjs +10 -0
  219. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  220. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  221. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  222. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  223. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  224. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  225. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  226. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  227. package/src/runtime/terminal-bridge/client.mjs +10 -1
  228. package/src/runtime/visualize-bridge/client.mjs +27 -8
  229. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  230. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  231. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  232. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  233. package/src/session-runtime/agent-home-delivery.mjs +448 -185
  234. package/src/session-runtime/agent-task-sessions.mjs +329 -180
  235. package/src/session-runtime/boot/apis.mjs +9 -0
  236. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  237. package/src/session-runtime/boot/providers.mjs +3 -0
  238. package/src/session-runtime/boot/tools.mjs +6 -1
  239. package/src/session-runtime/config-lifecycle.mjs +6 -0
  240. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  241. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  242. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  243. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  244. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  245. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  246. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  247. package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
  248. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  249. package/src/session-runtime/mixy-heartbeat.mjs +153 -33
  250. package/src/session-runtime/notification-bus.mjs +31 -117
  251. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  252. package/src/session-runtime/notification-listeners.mjs +76 -0
  253. package/src/session-runtime/owned-automation-run.mjs +2 -1
  254. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  255. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  256. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  257. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  258. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  259. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  260. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  261. package/src/session-runtime/routine-owner.mjs +40 -9
  262. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  263. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  264. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  265. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  266. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  267. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  268. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  269. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  270. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  271. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  272. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  273. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  274. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  275. package/src/session-runtime/services/channel-admin.mjs +7 -13
  276. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  277. package/src/session-runtime/services/seeds.mjs +0 -28
  278. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  279. package/src/session-runtime/session-create/session-options.mjs +1 -0
  280. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  281. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  282. package/src/session-runtime/settings-system-api.mjs +13 -1
  283. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  284. package/src/session-runtime/setup-tool/executor.mjs +145 -22
  285. package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
  286. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  287. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
  288. package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
  289. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  290. package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
  291. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  292. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  293. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  294. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  295. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  296. package/src/standalone/channel-transport.mjs +1 -1
  297. package/src/standalone/daemon-channels-loader.mjs +10 -8
  298. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  299. package/src/standalone/daemon-session-control/request.mjs +51 -0
  300. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  301. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  302. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  303. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  304. package/src/standalone/daemon-session-control.mjs +124 -188
  305. package/src/standalone/daemon.mjs +26 -3
  306. package/src/standalone/session-client.mjs +7 -3
  307. package/src/standalone/session-protocol.mjs +5 -0
  308. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  309. package/src/standalone/session-service/quota-resume.mjs +3 -0
  310. package/src/standalone/session-service/retention/busy.mjs +65 -1
  311. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  312. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  313. package/src/standalone/session-tool-output.mjs +15 -7
  314. package/src/tui/app/model-options.mjs +3 -28
  315. package/src/tui/dist/index.mjs +58 -64
  316. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  317. package/src/tui/session/initial-state.mjs +3 -0
  318. package/src/tui/session/notice-surface.mjs +7 -1
  319. package/src/tui/session/queue-helpers.mjs +3 -42
  320. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  321. package/src/tui/session/session-api/routes-write.mjs +5 -1
  322. package/src/tui/session/session-api/routes.mjs +1 -0
  323. package/src/tui/session/session-api/settings.mjs +17 -1
  324. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  325. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  326. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  327. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  328. package/src/tui/session/session-flow/drain.mjs +118 -149
  329. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  330. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  331. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  332. package/src/tui/session/tool-approval-request.mjs +39 -0
  333. package/src/tui/session/tool-approval-settle.mjs +62 -0
  334. package/src/tui/session/tool-approval.mjs +69 -115
  335. package/src/tui/session/tui-steering-persist.mjs +8 -1
  336. package/src/tui/session/turn-stream.mjs +19 -2
  337. package/src/tui/session/turn.mjs +1 -0
  338. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  339. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  340. package/scripts/code-graph-description-contract.mjs +0 -188
  341. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -7,8 +7,13 @@
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.
12
17
  *
13
18
  * An agent's send to any other session reports the same way, by an origin for
14
19
  * that send only (reportOrigin). Every reporting turn is also watched durably
@@ -79,12 +84,15 @@ const turnStatus = (detail) =>
79
84
 
80
85
  const INTERRUPTED = /^\[Request interrupted/;
81
86
 
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('');
87
+ const partText = (part) => {
88
+ if (typeof part === 'string') return part;
89
+ return part?.type === 'text' ? part.text || '' : '';
90
+ };
91
+
92
+ const messageText = (content) => {
93
+ if (typeof content === 'string') return content;
94
+ return (Array.isArray(content) ? content : []).map(partText).join('');
95
+ };
88
96
 
89
97
  /** The submissions a stored user message delivered: a turn's prompt
90
98
  * (`checkpointId`, `submissionIds` of a merged batch) or a steered message. */
@@ -106,13 +114,21 @@ export function submissionIdsOf(message) {
106
114
  export function submissionOutcome(messages, runId) {
107
115
  const list = Array.isArray(messages) ? messages : [];
108
116
  const start = list.findIndex((message) => submissionIdsOf(message).includes(runId));
109
- if (start < 0) return null;
117
+ return start < 0 ? null : outcomeFrom(list, start);
118
+ }
119
+
120
+ /** submissionOutcome of the turn whose prompt is `list[start]`, read in
121
+ * place: the scan stops at that turn's end (its completion, an
122
+ * interruption, or the next turn's prompt). */
123
+ function outcomeFrom(list, start) {
110
124
  for (let index = start; index < list.length; index += 1) {
111
125
  const message = list[index];
112
126
  const completion = message?.meta?.transcript?.completion;
113
127
  if (completion) {
114
128
  const status = turnStatus(completion);
115
- const text = status === 'failed' ? completion.detail || '' : message.role === 'assistant' ? message.content : '';
129
+ let text = '';
130
+ if (status === 'failed') text = completion.detail || '';
131
+ else if (message.role === 'assistant') text = message.content;
116
132
  return { status, text: messageText(text) };
117
133
  }
118
134
  if (index === start || message?.role !== 'user') continue;
@@ -142,7 +158,9 @@ const callArguments = (call) => {
142
158
  * no task. Empty when the turn recorded none. */
143
159
  function consumedTaskIds(message) {
144
160
  const recorded = message?.meta?.transcript?.consumedExecutionIds;
145
- const ids = Array.isArray(recorded) ? recorded : message?.meta?.execution?.id ? [message.meta.execution.id] : [];
161
+ let ids = [];
162
+ if (Array.isArray(recorded)) ids = recorded;
163
+ else if (message?.meta?.execution?.id) ids = [message.meta.execution.id];
146
164
  return ids.map((id) => (id ? agentTaskIdOfExecution(id) : null));
147
165
  }
148
166
 
@@ -164,7 +182,9 @@ export function latestTurnOutcome(messages, runId) {
164
182
  return chain && { id: chain.id, outcome: chain.outcome, at: chain.at, ambiguous: chain.ambiguous };
165
183
  }
166
184
 
167
- /** latestTurnOutcome with what a report's wait needs besides: `taskIds`, the
185
+ /** latestTurnOutcome with what a report's wait needs besides: `answers`,
186
+ * every turn of the chain that recorded its end, oldest first (`{ id,
187
+ * status, text }`: its submission id and submissionOutcome); `taskIds`, the
168
188
  * agent tasks the chain's turns started — the only tasks proven the chain's
169
189
  * — and `busyIsOthers`: the session's last stored turn is one outside the
170
190
  * chain that recorded no end yet, and a stored turn took the result of every
@@ -187,6 +207,8 @@ function turnChain(messages, runId) {
187
207
  // The turn the walk is in: where its prompt is stored and its submission id.
188
208
  let turnAt = start;
189
209
  let turnId = runId;
210
+ // Every turn of the chain, in order: where its prompt is stored, its id.
211
+ const turns = [{ at: start, id: runId }];
190
212
  for (let index = start; index < list.length; index += 1) {
191
213
  const message = list[index];
192
214
  const checkpointId = index > start && message?.role === 'user' ? message.meta?.transcript?.checkpointId : '';
@@ -209,6 +231,7 @@ function turnChain(messages, runId) {
209
231
  inChain = true;
210
232
  at = turnAt;
211
233
  id = turnId;
234
+ if (turns.at(-1).id !== turnId) turns.push({ at: turnAt, id: turnId });
212
235
  } else if (!delivered.length || delivered.includes(null)) {
213
236
  ambiguous = true;
214
237
  }
@@ -227,14 +250,27 @@ function turnChain(messages, runId) {
227
250
  if (Array.isArray(message.agentTaskIds)) for (const taskId of message.agentTaskIds) started.add(String(taskId));
228
251
  else if (message.toolKind !== 'error') ambiguous = true;
229
252
  }
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);
253
+ // Read in place, never on copied suffixes: each chain turn's outcome scan
254
+ // stops at that turn's end, so the turns' scans cover disjoint stretches
255
+ // and the whole walk stays linear in the transcript.
256
+ let ended = null;
257
+ for (let index = at; index < list.length && !ended; index += 1) {
258
+ if (list[index]?.meta?.transcript?.completion) ended = list[index];
259
+ }
260
+ const last = turnId === id ? null : outcomeFrom(list, turnAt);
261
+ const answers = [];
262
+ let outcome = null;
263
+ for (const chainTurn of turns) {
264
+ const turnOutcome = outcomeFrom(list, chainTurn.at);
265
+ if (chainTurn.at === at) outcome = turnOutcome;
266
+ if (turnOutcome !== 'open') answers.push({ id: chainTurn.id, ...turnOutcome });
267
+ }
233
268
  return {
234
269
  id,
235
- outcome: submissionOutcome(turn, id),
270
+ outcome,
236
271
  at: Number(ended?.meta?.transcript?.at) || 0,
237
272
  ambiguous,
273
+ answers,
238
274
  taskIds: started,
239
275
  busyIsOthers: last === 'open' && [...started].every((taskId) => taken.has(taskId)),
240
276
  };
@@ -333,7 +369,7 @@ function chainPendingWork(sessionId, taskIds, { owed, pool, agentTaskOwners, wor
333
369
  /** The agent results queued for `sessionId` that no turn has taken yet (its
334
370
  * durable pending queue: an agent's completion, a restart failure): `{ text,
335
371
  * meta }` as the notification bus delivers them. */
336
- export async function owedAgentResults(sessionId) {
372
+ async function owedAgentResults(sessionId) {
337
373
  const [{ _getPendingMessagesForSession }, { completionIsDelivered, isCompletionNotificationEntry }] =
338
374
  await Promise.all([
339
375
  import('../runtime/agent/orchestrator/session/manager/pending-messages.mjs'),
@@ -399,9 +435,6 @@ const LOST_TEXT = 'The message never ran: the app restarted before its turn star
399
435
  const GONE_TEXT = 'The session no longer exists.';
400
436
  const RESUME_FAILED_TEXT =
401
437
  '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
438
  // Deliveries of a restarted task session's queued agent results before its
406
439
  // report gives up as RESUME_FAILED_TEXT.
407
440
  const OWED_WAKE_TRIES = 3;
@@ -472,27 +505,54 @@ async function lastUsedSessionRoute(sessionService) {
472
505
  * without the provenance to tell (no stored turn, `ambiguous`) the whole
473
506
  * session must be idle with none pending. `immediate` is runId's own end,
474
507
  * 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. */
508
+ * Every other answer of the chain — each ended turn a later turn of the
509
+ * chain followed, and the latest while its work still runs or its
510
+ * provenance is missing — goes to `interim({ id, status, text })` once, in
511
+ * chain order (`id`: the submission of the turn that gave it): a progress
512
+ * update, which wakes nobody. Every check lists all the chain's ended turns,
513
+ * so answers that end close together are each reported. `progressedThrough`:
514
+ * the last turn an earlier process reported (the watch's durable mark):
515
+ * it and the turns before it are not reported again.
516
+ * `finish(status, text, answerId)` runs once (`answerId`: the turn whose
517
+ * answer it is, none for a report of no turn); `check` re-evaluates (call it
518
+ * on every state change of `runtime`), `stop` ends the wait. */
477
519
  function followTaskToFinal(
478
- { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
479
- { sessionId, runId, runtime, immediate = null, wakeOwed = false, finish, progress = null }
520
+ { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, log },
521
+ { sessionId, runId, runtime, immediate = null, wakeOwed = false, finish, interim = null, progressedThrough = '' }
480
522
  ) {
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}`);
523
+ // The chain's ended answers, oldest first, `{ id, status, text }`; runId's
524
+ // own is `immediate` when this process saw it. `latest`: turnChain's.
525
+ const answersOf = (latest) => {
526
+ const stored = latest?.answers || [];
527
+ if (!immediate) return stored;
528
+ return [{ id: runId, ...immediate }, ...stored.filter((answer) => answer.id !== runId)];
529
+ };
530
+ // The answers already handed to `interim`, by turn.
531
+ const interimIds = new Set();
532
+ /** Hand `answer` to `interim` once; false when that failed (logged): it is
533
+ * tried again at the next check, and no later answer goes out before it. */
534
+ async function reportInterim(answer) {
535
+ if (!answer?.id || !String(answer.text || '').trim() || typeof interim !== 'function') return true;
536
+ if (interimIds.has(answer.id)) return true;
537
+ try {
538
+ await interim(answer);
539
+ } catch (error) {
540
+ log(`progress update of ${sessionId} failed, retried at the next check: ${error?.message || error}`);
541
+ return false;
542
+ }
543
+ interimIds.add(answer.id);
544
+ return true;
545
+ }
546
+ /** Report, in order, every one of `answers` past the durable mark but
547
+ * `held` (the final candidate, reported only when it proves not final);
548
+ * false, stopping there, at the first that could not be queued. */
549
+ async function reportEarlier(answers, held) {
550
+ const through = progressedThrough ? answers.findIndex((answer) => answer.id === progressedThrough) : -1;
551
+ for (const [index, answer] of answers.entries()) {
552
+ if (index <= through) interimIds.add(answer.id);
553
+ else if (answer !== held && !(await reportInterim(answer))) return false;
554
+ }
555
+ return true;
496
556
  }
497
557
  let done = false;
498
558
  let checking = false;
@@ -553,6 +613,14 @@ function followTaskToFinal(
553
613
  const latest = turnChain(stored?.messages, runId);
554
614
  // The chain's agent tasks; null (the whole session) without its provenance.
555
615
  const taskIds = latest && !latest.ambiguous ? latest.taskIds : null;
616
+ // Every answer of the chain but its latest turn's is no final one: each
617
+ // goes out as progress, whatever the session does now.
618
+ const answers = answersOf(latest);
619
+ const latestId = latest ? latest.id : runId;
620
+ const answer = answers.at(-1)?.id === latestId ? answers.at(-1) : null;
621
+ // An answer that could not be queued holds the chain (later answers, the
622
+ // final report) until a later check queues it: the poll retries it.
623
+ if (!(await reportEarlier(answers, answer))) return null;
556
624
  // A busy session may run the chain's turn unless that turn is provably another's.
557
625
  const busy = Boolean(runtime?.getState?.()?.busy);
558
626
  if (busy && !(taskIds && latest.busyIsOthers)) return null;
@@ -561,18 +629,16 @@ function followTaskToFinal(
561
629
  if (woke !== undefined) return woke;
562
630
  }
563
631
  if (await pendingWork(sessionId, { taskIds })) {
564
- await reportProgress(latest);
632
+ await reportInterim(answer);
565
633
  return null;
566
634
  }
567
635
  if (!stored && !immediate) return { key: 'gone', status: 'cancelled', text: GONE_TEXT };
568
636
  // Provenance the chain needs is missing: its answer may not be final.
569
637
  if (latest?.ambiguous) {
570
- await reportProgress(latest);
638
+ await reportInterim(answer);
571
639
  return null;
572
640
  }
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 };
641
+ return answer && { key: `${answer.id}:${answer.status}`, ...answer };
576
642
  }
577
643
  async function check() {
578
644
  if (done) return;
@@ -598,7 +664,7 @@ function followTaskToFinal(
598
664
  arm(candidateAt + settleMs - Date.now());
599
665
  } else {
600
666
  stop();
601
- await finish(end.status, end.text);
667
+ await finish(end.status, end.text, end.id);
602
668
  }
603
669
  } while (again && !done);
604
670
  } finally {
@@ -756,7 +822,7 @@ async function rearmTaskWatch(
756
822
  // The turn that answers: the watched submission's own, or the one it
757
823
  // follows (a room member's turn under another submission id).
758
824
  const turnId = watch.follows || watch.runId;
759
- const finish = async (status, text = '') =>
825
+ const finish = async (status, text = '', answerId = '') =>
760
826
  deliver(watch.agentId, {
761
827
  runId: `${watch.runId}_end`,
762
828
  taskSessionId: sessionId,
@@ -767,6 +833,7 @@ async function rearmTaskWatch(
767
833
  ...(watch.hop > 0 ? { hop: watch.hop } : {}),
768
834
  ...roomFrom(watch.homeSessionId),
769
835
  watchId: watch.runId,
836
+ ...progressOf(watch.runId, answerId),
770
837
  ...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT ? {} : await artifactFields(sessionId, turnId)),
771
838
  });
772
839
  if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
@@ -812,21 +879,25 @@ async function rearmTaskWatch(
812
879
  runId: turnId,
813
880
  runtime,
814
881
  wakeOwed: true,
815
- progress: (text) =>
882
+ // The turns an earlier process already reported as progress.
883
+ progressedThrough: watch.progressed || '',
884
+ interim: (answer) =>
816
885
  deliver(watch.agentId, {
817
- runId: `${watch.runId}_progress`,
886
+ runId: progressRunId(watch.runId, answer.id),
818
887
  taskSessionId: sessionId,
819
888
  title: watch.title,
820
889
  status: 'working',
821
- text,
890
+ text: answer.text,
891
+ progressWatchId: watch.runId,
892
+ answerId: answer.id,
822
893
  ...(watch.target === 'session' ? { target: 'session' } : {}),
823
894
  ...(watch.hop > 0 ? { hop: watch.hop } : {}),
824
895
  ...roomFrom(watch.homeSessionId),
825
896
  }),
826
- finish: async (status, text) => {
897
+ finish: async (status, text, answerId) => {
827
898
  done = true;
828
899
  unsubscribe();
829
- await finish(status, text);
900
+ await finish(status, text, answerId);
830
901
  },
831
902
  });
832
903
  } else if (end) {
@@ -845,6 +916,161 @@ async function rearmTaskWatch(
845
916
  await check();
846
917
  }
847
918
 
919
+ /** The settlement of sends one turn answers: they settle together with the
920
+ * same `detail` object (a send steered into the turn while it ran, or queued
921
+ * and taken as one batch), and that turn reports once, closing every one of
922
+ * them. `nextSeq()` numbers the sends in submission order: the earliest of
923
+ * the sends one turn answers reports for them. */
924
+ function createSendSettlement(log) {
925
+ const settledTurns = new WeakMap();
926
+ let sendCount = 0;
927
+
928
+ /** Add send `member` (`{ seq, id, unwatch, settle }`) to the settlement
929
+ * `detail` of its turn, per reporting conversation. The sends of one turn
930
+ * settle in the same tick; after it the earliest (the turn's own, whose
931
+ * record holds the whole turn) settles with all their ids and the others
932
+ * stand down. */
933
+ function join(detail, origin, member) {
934
+ const key = `${origin.agentId}\0${origin.sessionId}`;
935
+ let groups = settledTurns.get(detail);
936
+ if (!groups) {
937
+ groups = new Map();
938
+ settledTurns.set(detail, groups);
939
+ }
940
+ let members = groups.get(key);
941
+ if (!members) {
942
+ members = [];
943
+ groups.set(key, members);
944
+ const settling = members;
945
+ void Promise.resolve()
946
+ .then(() => {
947
+ const [first, ...others] = settling.sort((a, b) => a.seq - b.seq);
948
+ for (const other of others) other.unwatch();
949
+ first.settle(settling.map((entry) => entry.id));
950
+ })
951
+ .catch((error) => log(`settling the sends of one turn failed: ${error?.message || error}`));
952
+ }
953
+ members.push(member);
954
+ }
955
+
956
+ const nextSeq = () => {
957
+ sendCount += 1;
958
+ return sendCount;
959
+ };
960
+
961
+ return { join, nextSeq };
962
+ }
963
+
964
+ /** The queue row of send `id`'s progress update carrying the answer of turn
965
+ * `answerId` (followToFinal `interim`): one per answer, across restarts. */
966
+ const progressRunId = (id, answerId) => `${id}_progress_${answerId}`;
967
+
968
+ /** The queue row of send `id`'s report of `status`. 'working': a progress
969
+ * update (progressRunId), not the end. A wait for approval belongs to the
970
+ * target session, not to this send: every watcher of it names the same row,
971
+ * so it posts once. */
972
+ const turnReportRunId = (sessionId, id, status, { approvalId = '', answerId = '' } = {}) => {
973
+ if (status === 'blocked') return `${sessionId}_${approvalId}_blocked`;
974
+ return status === 'working' ? progressRunId(id, answerId) : `${id}_end`;
975
+ };
976
+
977
+ /** The report fields naming the progress update that may have carried the
978
+ * final answer of turn `answerId` already (the queue then does not repeat
979
+ * it); none for a report of no turn. */
980
+ const progressOf = (id, answerId) => (answerId ? { progressRunId: progressRunId(id, answerId) } : {});
981
+
982
+ /** The handler of send `id`'s task-session state: a wait for the user's
983
+ * approval reports blocked once; a later, different one reports again. */
984
+ function approvalWaitReporter(id, report) {
985
+ // The approval wait last reported.
986
+ let reportedApproval = null;
987
+ return (state) => {
988
+ if (!state.toolApproval) {
989
+ reportedApproval = null;
990
+ return;
991
+ }
992
+ const approvalId = String(state.toolApproval.id ?? 'approval');
993
+ if (approvalId === reportedApproval) return;
994
+ // A send still queued behind the running turn is not the turn
995
+ // waiting: that turn's own watcher reports it.
996
+ if ((state.queued || []).some((entry) => entry?.id === id)) return;
997
+ reportedApproval = approvalId;
998
+ void report('blocked', '', { approvalId });
999
+ };
1000
+ }
1001
+
1002
+ /** The transcript meta of a send from `origin`. `fromAgent`: the sending bot,
1003
+ * recorded when it writes into another bot's home chat (otherwise the row
1004
+ * would read as the user's); `hop`: its place in a bot-to-bot chain
1005
+ * (bot-hops.mjs). */
1006
+ const sendTranscriptMeta = (origin) => ({
1007
+ sender: 'lead',
1008
+ ...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
1009
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1010
+ // The bots that handed this work on, the sender last (bot-hops.mjs).
1011
+ ...(origin.botChain?.length ? { botChain: origin.botChain } : {}),
1012
+ });
1013
+
1014
+ /** What a send tells its caller once submitted. */
1015
+ const sendResult = (accepted, notify, id, noReport) => ({
1016
+ accepted,
1017
+ // Names this send; its terminal report carries it back (routine.sendId).
1018
+ ...(accepted && notify ? { sendId: id } : {}),
1019
+ reportsBack: accepted && notify,
1020
+ ...(accepted && !notify ? { reportsBackReason: noReport } : {}),
1021
+ });
1022
+
1023
+ /** create's brief and `worktree` location, checked before anything runs. */
1024
+ function createRequest(args, callerAgentId, callerScope) {
1025
+ if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
1026
+ const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
1027
+ if (!brief) throw new Error('session: brief is required for action=create');
1028
+ const location = taskLocation(args.worktree);
1029
+ if (location.mode === 'worktree') assertWorktreeMutationAllowed(callerScope, 'session: create with a worktree');
1030
+ return { brief, location };
1031
+ }
1032
+
1033
+ /** Where create starts (resolveTaskProject). An explicit worktree is never
1034
+ * silently dropped: it needs a registered Project. */
1035
+ function createWhere(args, projects, callerCwd, location) {
1036
+ const where = resolveTaskProject(args.project, { projects, cwd: callerCwd || process.cwd() });
1037
+ const registered = where.project && projects.some((entry) => samePath(entry.path, where.project.path));
1038
+ if (location.mode === 'worktree' && !registered) {
1039
+ throw taskWorktreeFailure(
1040
+ worktreeError('WORKTREE_PROJECT_UNKNOWN', 'A worktree needs a registered Project; run list-projects.', {
1041
+ projectPath: where.project?.path ?? null,
1042
+ })
1043
+ );
1044
+ }
1045
+ return where;
1046
+ }
1047
+
1048
+ /** The worktree failure `error` of task session `sessionId` whose empty
1049
+ * session could not be dropped (`dropFailure`): the model must know the
1050
+ * empty session is still there to delete. */
1051
+ const orphanSessionError = (error, sessionId, dropFailure) =>
1052
+ Object.assign(
1053
+ new Error(
1054
+ `${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}.`
1055
+ ),
1056
+ {
1057
+ ...(typeof error?.code === 'string' ? { code: error.code } : {}),
1058
+ ...(error?.details ? { details: error.details } : {}),
1059
+ orphanSessionId: sessionId,
1060
+ cause: error,
1061
+ }
1062
+ );
1063
+
1064
+ /** `ready` resolves once `started()` runs: "Started" lands in the home before
1065
+ * any report of the brief's turn. */
1066
+ function startedGate() {
1067
+ let started = () => {};
1068
+ const ready = new Promise((resolveReady) => {
1069
+ started = resolveReady;
1070
+ });
1071
+ return { ready, started };
1072
+ }
1073
+
848
1074
  export function createAgentTaskSessions({
849
1075
  getSessionService,
850
1076
  deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
@@ -868,7 +1094,7 @@ export function createAgentTaskSessions({
868
1094
  (await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).worktree?.newTaskDefault,
869
1095
  modelRoute = configModelRoute,
870
1096
  // The model menu's catalog rows, read through the calling session's runtime.
871
- modelCatalog = (sessionId) => readModelCatalog(service(), sessionId),
1097
+ modelCatalog = (sessionId) => readModelCatalog(getSessionService(), sessionId),
872
1098
  // The Lead (Main) route of the config — what a desktop New task starts on.
873
1099
  leadRoute = configLeadRoute,
874
1100
  // The model the user last used in a coding session.
@@ -899,9 +1125,6 @@ export function createAgentTaskSessions({
899
1125
  owedResults = owedAgentResults,
900
1126
  deliverCompletion = null,
901
1127
  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
1128
  log = (line) => process.stderr.write(`mixdog task sessions: ${line}\n`),
906
1129
  }) {
907
1130
  /** The report fields of turn `runId`'s files; none when it produced none. */
@@ -923,44 +1146,9 @@ export function createAgentTaskSessions({
923
1146
  };
924
1147
 
925
1148
  const followToFinal = (follow) =>
926
- followTaskToFinal(
927
- { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
928
- follow
929
- );
930
-
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;
1149
+ followTaskToFinal({ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, log }, follow);
936
1150
 
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
- }
1151
+ const settlement = createSendSettlement(log);
964
1152
 
965
1153
  /** Submit `prompt` as the origin agent's turn; its end (and a wait for the
966
1154
  * user in it) reports to the agent's home. Resolves on intake. `submit`
@@ -982,41 +1170,49 @@ export function createAgentTaskSessions({
982
1170
  watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
983
1171
  let runtime = null;
984
1172
  // The sends this turn's end closes: this one, plus any that its turn
985
- // answered too (joinSettlement).
1173
+ // answered too (createSendSettlement).
986
1174
  let sendIds = [id];
987
1175
  // A report that cannot be queued is retried in this process; when every try
988
1176
  // fails the durable watch stays and restart recovery delivers it (the queue
989
1177
  // 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}`));
1178
+ // `answerId`: the turn whose answer a progress update or the end carries.
1179
+ // `deliverReport` rejects once every try failed (a progress update is
1180
+ // tried again at the follow's next check); `report` logs it instead.
1181
+ const deliverReport = (status, text = '', { approvalId = '', answerId = '' } = {}) =>
1182
+ Promise.resolve(beforeReport).then(async () =>
1183
+ deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
1184
+ runId: turnReportRunId(sessionId, id, status, { approvalId, answerId }),
1185
+ taskSessionId: sessionId,
1186
+ title: origin.title,
1187
+ status,
1188
+ text,
1189
+ ...target,
1190
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1191
+ ...roomFrom(origin.sessionId),
1192
+ // A progress update moves this send's durable mark to its turn
1193
+ // (agent-home-delivery.mjs enqueueTask), in the same write.
1194
+ ...(status === 'working' ? { progressWatchId: id, answerId } : {}),
1195
+ // A turn's end brings back the files it produced, when the agent
1196
+ // may receive them (reportOrigin `textOnly`).
1197
+ ...(status === 'blocked' || status === 'working'
1198
+ ? {}
1199
+ : {
1200
+ watchId: id,
1201
+ ...(sendIds.length > 1 ? { watchIds: sendIds } : {}),
1202
+ ...progressOf(id, answerId),
1203
+ ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)),
1204
+ }),
1205
+ })
1206
+ );
1207
+ const report = (status, text = '', options = {}) =>
1208
+ deliverReport(status, text, options).catch((error) =>
1209
+ log(`report of ${sessionId} failed: ${error?.message || error}`)
1210
+ );
1015
1211
  let unwatch = () => {};
1016
1212
  // Set once the turn ended completed: the report waits for the final answer.
1017
1213
  let follow = null;
1018
1214
  let result;
1019
- /** This send ends the turn `detail` for the sends `answered` (joinSettlement). */
1215
+ /** This send ends the turn `detail` for the sends `answered` (createSendSettlement). */
1020
1216
  const settleTurn = (detail, answered) => {
1021
1217
  sendIds = answered;
1022
1218
  const status = turnStatus(detail);
@@ -1033,24 +1229,23 @@ export function createAgentTaskSessions({
1033
1229
  runId: id,
1034
1230
  runtime,
1035
1231
  immediate: { status, text },
1036
- finish: async (endStatus, endText) => {
1232
+ finish: async (endStatus, endText, answerId) => {
1037
1233
  unwatch();
1038
- await report(endStatus, endText);
1234
+ await report(endStatus, endText, { answerId });
1039
1235
  },
1040
- progress: (progressText) => report('working', progressText),
1236
+ // Each other answer of its chain: a progress update that wakes nobody.
1237
+ interim: (answer) => deliverReport('working', answer.text, { answerId: answer.id }),
1041
1238
  });
1042
1239
  follow.check();
1043
1240
  };
1044
1241
  try {
1045
1242
  if (notify) {
1046
1243
  runtime = await sessions.materializeSession(sessionId);
1047
- let blocked = false;
1244
+ const onState = approvalWaitReporter(id, report);
1048
1245
  unwatch =
1049
1246
  runtime?.subscribe?.(() => {
1050
1247
  follow?.check();
1051
- if (blocked || !runtime.getState?.()?.toolApproval) return;
1052
- blocked = true;
1053
- void report('blocked');
1248
+ onState(runtime.getState?.() || {});
1054
1249
  }) || unwatch;
1055
1250
  }
1056
1251
  // A bot's send to a session that is not its own task session reaches the
@@ -1059,22 +1254,11 @@ export function createAgentTaskSessions({
1059
1254
  ? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
1060
1255
  : prompt;
1061
1256
  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;
1257
+ const seq = settlement.nextSeq();
1065
1258
  result = await take({
1066
1259
  id,
1067
1260
  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
- },
1261
+ transcriptMeta: sendTranscriptMeta(origin),
1078
1262
  ...(notify
1079
1263
  ? {
1080
1264
  // A room answers this send in a turn under another submission
@@ -1082,7 +1266,7 @@ export function createAgentTaskSessions({
1082
1266
  // short): the watch follows that turn, recorded before it runs.
1083
1267
  follow: (submissionId) => watches.follow?.(id, submissionId),
1084
1268
  onSettled: (detail) =>
1085
- joinSettlement(detail, origin, {
1269
+ settlement.join(detail, origin, {
1086
1270
  seq,
1087
1271
  id,
1088
1272
  unwatch: () => unwatch(),
@@ -1101,13 +1285,7 @@ export function createAgentTaskSessions({
1101
1285
  unwatch();
1102
1286
  if (notify) await dropWatch();
1103
1287
  }
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
- };
1288
+ return sendResult(accepted, notify, id, noReport);
1111
1289
  }
1112
1290
 
1113
1291
  const rearm = (watch) =>
@@ -1160,22 +1338,8 @@ export function createAgentTaskSessions({
1160
1338
  attachmentParts = [],
1161
1339
  } = {}
1162
1340
  ) {
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
- }
1341
+ const { brief, location } = createRequest(args, callerAgentId, callerScope);
1342
+ const where = createWhere(args, await listProjects(), callerCwd, location);
1179
1343
  const requestId = `atask_${randomUUID()}`;
1180
1344
  const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
1181
1345
  const taskOrigin = {
@@ -1243,25 +1407,10 @@ export function createAgentTaskSessions({
1243
1407
  }
1244
1408
  if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
1245
1409
  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
- );
1410
+ throw orphanSessionError(error, sessionId, dropFailure);
1258
1411
  }
1259
1412
  }
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
- });
1413
+ const { ready, started } = startedGate();
1265
1414
  // The brief carries the files the agent forwarded (workspace-tools.mjs).
1266
1415
  const prompt = handoffContent(brief, attachmentParts);
1267
1416
  const botChain = botChainOf(callerBotChain);