mixdog 1.3.1 → 1.3.3

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 (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -15,6 +15,16 @@
15
15
  * (agent-home-delivery.mjs `watches`) until its end is queued, so a daemon
16
16
  * restart before that re-attaches it (recover): an ended turn reports once
17
17
  * from the target's record, a queued or running one is watched to its end.
18
+ *
19
+ * A group-room member (its member session `sess_rmem_<room>_<agent>`) opens
20
+ * and drives task sessions the same way; its reports carry `from` (the member
21
+ * session), so they land in that member session alone, never in its home or
22
+ * the room's log, and an ended turn gives the member a turn in its room to
23
+ * relay it (agent-home-delivery.mjs enqueueTask).
24
+ *
25
+ * A send to a group room's log goes to one member through the desktop room
26
+ * host (daemon-session-control.mjs): the member's turn in its member session
27
+ * is the reporting turn, taken by the room (`submit`), not by a prompt.
18
28
  */
19
29
  import { randomUUID } from 'node:crypto';
20
30
  import { realpathSync, statSync } from 'node:fs';
@@ -22,6 +32,12 @@ import { basename, resolve } from 'node:path';
22
32
  import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
23
33
  import { collectTurnArtifacts, handoffContent, sessionRoots, turnSlice } from './handoff-attachments.mjs';
24
34
  import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
35
+ import { wrapAgentMessage } from '../runtime/shared/agent-message-envelope.mjs';
36
+ import { sessionsWithPendingWork } from '../runtime/shared/pending-agent-work.mjs';
37
+ import { agentTaskIdOfExecution } from './services/agent-tool/restart-notify.mjs';
38
+ import { worktreeError, worktreeNameError } from '../runtime/shared/worktrees/contract.mjs';
39
+ import { samePath } from '../runtime/shared/worktrees/paths.mjs';
40
+ import { assertWorktreeMutationAllowed } from './services/worktree-access.mjs';
25
41
 
26
42
  const TITLE_LIMIT = 60;
27
43
 
@@ -105,12 +121,600 @@ export function submissionOutcome(messages, runId) {
105
121
  return 'open';
106
122
  }
107
123
 
124
+ // `agent` tool calls that can start a job (a spawn, or a send that begins one).
125
+ const STARTING_AGENT_CALLS = new Set(['spawn', 'send']);
126
+
127
+ const callArguments = (call) => {
128
+ const raw = call?.arguments ?? call?.function?.arguments;
129
+ if (raw && typeof raw === 'object') return raw;
130
+ try {
131
+ return JSON.parse(String(raw || '{}')) || {};
132
+ } catch {
133
+ return {};
134
+ }
135
+ };
136
+
137
+ /** The agent task ids a woken turn's prompt delivered, from its stored
138
+ * provenance: the batch's `consumedExecutionIds` (session-flow/drain.mjs)
139
+ * or the single `execution` of a queue-fed turn; null for an id that names
140
+ * no task. Empty when the turn recorded none. */
141
+ function consumedTaskIds(message) {
142
+ const recorded = message?.meta?.transcript?.consumedExecutionIds;
143
+ const ids = Array.isArray(recorded) ? recorded : message?.meta?.execution?.id ? [message.meta.execution.id] : [];
144
+ return ids.map((id) => (id ? agentTaskIdOfExecution(id) : null));
145
+ }
146
+
147
+ /** The latest turn of the chain that submission `runId` started in a
148
+ * session's stored `messages`, from structured provenance only: runId's own
149
+ * turn, then each turn that took a result of an agent task the chain started
150
+ * — as its prompt (a woken turn) or steered into it while it ran, even a turn
151
+ * the user started (its `agent` spawn/send results record
152
+ * `agentTaskIds`, tool-batch/finalize.mjs; the tasks those turns start join
153
+ * the chain). A turn the user typed, or one woken only by other agents,
154
+ * never joins it. `ambiguous` is set when provenance a chain decision needs
155
+ * is missing — a starting agent call of the chain without `agentTaskIds`,
156
+ * or a woken turn naming no task — so its answer may not be the final one.
157
+ * `{ id, outcome, at, ambiguous }`: that turn's submission id, its
158
+ * submissionOutcome and when its end was recorded (0 when unknown); null
159
+ * when no turn took runId. */
160
+ export function latestTurnOutcome(messages, runId) {
161
+ const list = Array.isArray(messages) ? messages : [];
162
+ const start = list.findIndex((message) => submissionIdsOf(message).includes(runId));
163
+ if (start < 0) return null;
164
+ const started = new Set();
165
+ const startingCalls = new Set();
166
+ let at = start;
167
+ let id = runId;
168
+ let inChain = true;
169
+ let ambiguous = false;
170
+ // The turn the walk is in: where its prompt is stored and its submission id.
171
+ let turnAt = start;
172
+ let turnId = runId;
173
+ for (let index = start; index < list.length; index += 1) {
174
+ const message = list[index];
175
+ const checkpointId = index > start && message?.role === 'user' ? message.meta?.transcript?.checkpointId : '';
176
+ if (checkpointId) {
177
+ turnAt = index;
178
+ turnId = checkpointId;
179
+ inChain = false;
180
+ }
181
+ // A completion delivered as a turn's prompt (queue/bus wake), or steered
182
+ // into a turn already running (loop/steering.mjs, no checkpoint of its
183
+ // own): the turn that took the chain's result joins the chain, even one
184
+ // the user started, so its answer becomes the candidate.
185
+ const delivered =
186
+ index > start && message?.role === 'user' && message.meta?.source === 'task-notification'
187
+ ? consumedTaskIds(message)
188
+ : null;
189
+ if (delivered && !inChain) {
190
+ if (delivered.some((taskId) => taskId && started.has(taskId))) {
191
+ inChain = true;
192
+ at = turnAt;
193
+ id = turnId;
194
+ } else if (!delivered.length || delivered.includes(null)) {
195
+ ambiguous = true;
196
+ }
197
+ }
198
+ if (checkpointId || delivered) continue;
199
+ if (!inChain) continue;
200
+ if (message?.role === 'assistant') {
201
+ for (const call of Array.isArray(message.toolCalls) ? message.toolCalls : []) {
202
+ const name = String(call?.name ?? call?.function?.name ?? '');
203
+ const type = String(callArguments(call).type || 'spawn').trim();
204
+ if (name === 'agent' && STARTING_AGENT_CALLS.has(type) && call?.id) startingCalls.add(call.id);
205
+ }
206
+ continue;
207
+ }
208
+ if (message?.role !== 'tool' || !startingCalls.has(message.toolCallId)) continue;
209
+ if (Array.isArray(message.agentTaskIds)) for (const taskId of message.agentTaskIds) started.add(String(taskId));
210
+ else if (message.toolKind !== 'error') ambiguous = true;
211
+ }
212
+ const turn = list.slice(at);
213
+ const ended = turn.find((message) => message?.meta?.transcript?.completion);
214
+ return { id, outcome: submissionOutcome(turn, id), at: Number(ended?.meta?.transcript?.at) || 0, ambiguous };
215
+ }
216
+
217
+ /** Whether task session `sessionId` still waits on agent work it handed off,
218
+ * from `agentWorking` (Lead ids with an Agent session in flight), `owed`
219
+ * (agent results queued for it that no turn has taken yet), `pool` (agent
220
+ * pool rows: the desktop notifier's rule, pending-agent-work.mjs) and
221
+ * `agentTaskOwners` (the sessions owning a queued, spawning or running agent
222
+ * task — one still waiting for admission has no turn in flight, and its pool
223
+ * row reads `unknown`). An agent task owned by an Agent session under it, at
224
+ * any depth, counts too: every descendant's pool row names the Lead as owner. */
225
+ export function pendingWorkOf(
226
+ sessionId,
227
+ { agentWorking = new Set(), owed = [], pool = [], agentTaskOwners = new Set() }
228
+ ) {
229
+ if (agentWorking.has(sessionId) || owed.length > 0 || agentTaskOwners.has(sessionId)) return true;
230
+ if (sessionsWithPendingWork(pool).has(sessionId)) return true;
231
+ return pool.some((row) => row?.ownerSessionId === sessionId && agentTaskOwners.has(String(row.sessionId || '')));
232
+ }
233
+
234
+ /** The agent results queued for `sessionId` that no turn has taken yet (its
235
+ * durable pending queue: an agent's completion, a restart failure): `{ text,
236
+ * meta }` as the notification bus delivers them. */
237
+ export async function owedAgentResults(sessionId) {
238
+ const [{ _getPendingMessagesForSession }, { completionIsDelivered, isCompletionNotificationEntry }] =
239
+ await Promise.all([
240
+ import('../runtime/agent/orchestrator/session/manager/pending-messages.mjs'),
241
+ import('../runtime/agent/orchestrator/session/manager/pending-message-entry.mjs'),
242
+ ]);
243
+ return _getPendingMessagesForSession(sessionId)
244
+ .filter((entry) => isCompletionNotificationEntry(entry) && !completionIsDelivered(entry))
245
+ .map((entry) => ({
246
+ text: String(entry.text || entry.content || ''),
247
+ meta: {
248
+ type: 'agent_task_result',
249
+ execution_surface: entry.execution?.surface || 'agent',
250
+ execution_id: entry.executionId || entry.execution?.id || null,
251
+ status: entry.execution?.status || null,
252
+ caller_session_id: sessionId,
253
+ },
254
+ }));
255
+ }
256
+
257
+ /** pendingWorkOf over this process's state: the in-flight runtimes, the
258
+ * durable pending queue, the agent pool and the background agent tasks. */
259
+ export async function taskSessionPendingWork(sessionId, { owedResults = owedAgentResults } = {}) {
260
+ const [{ inFlightSessionActivity }, { listStoredAgentWorkersAsync }, { activeBackgroundTaskSessionIds }] =
261
+ await Promise.all([
262
+ import('../runtime/agent/orchestrator/session/manager/runtime-state.mjs'),
263
+ import('../runtime/agent/orchestrator/session/store-summary-reader.mjs'),
264
+ import('../runtime/shared/background-tasks.mjs'),
265
+ ]);
266
+ const { working, agentWorking } = inFlightSessionActivity();
267
+ const agentTaskOwners = activeBackgroundTaskSessionIds({ surface: 'agent' });
268
+ if (pendingWorkOf(sessionId, { agentWorking, agentTaskOwners })) return true;
269
+ if ((await owedResults(sessionId)).length) return true;
270
+ const pool = await listStoredAgentWorkersAsync({ runningSessionIds: working });
271
+ return pendingWorkOf(sessionId, { pool, agentTaskOwners });
272
+ }
273
+
274
+ // A task session idle with nothing pending must stay so this long before its
275
+ // latest answer is final: a finished agent's result can start the Lead's next
276
+ // turn right after (the desktop notifier's quiet period).
277
+ const FINAL_SETTLE_MS = 2500;
278
+ // While a report waits on pending work it re-checks this often (state changes
279
+ // of the session re-check at once).
280
+ const FINAL_POLL_MS = 15_000;
281
+
282
+ const REPORT_RETRY_DELAYS_MS = [200, 1000, 5000];
283
+
108
284
  const LOST_TEXT = 'The message never ran: the app restarted before its turn started.';
109
285
  const GONE_TEXT = 'The session no longer exists.';
286
+ const RESUME_FAILED_TEXT =
287
+ 'The agent task session could not continue after the app restarted: its agents’ results never reached it. Check that session.';
288
+ const PENDING_CAP_MS = 30 * 60_000;
289
+ const STILL_WORKING_TEXT =
290
+ '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.';
291
+ // Deliveries of a restarted task session's queued agent results before its
292
+ // report gives up as RESUME_FAILED_TEXT.
293
+ const OWED_WAKE_TRIES = 3;
294
+
295
+ // Why a send or create reports nothing back (its `reportsBackReason`).
296
+ const NOTIFY_OFF_REASON = 'this agent task session was created with notify: false, so its turns do not report back';
297
+ const WATCH_CAP_REASON =
298
+ 'this chat already waits on as many unreported session results as it can track; earlier ones must report first';
110
299
 
111
300
  // The last-used model search stops after this many catalog pages.
112
301
  const LAST_USED_ROUTE_MAX_PAGES = 20;
113
302
 
303
+ /** A report's `from` when its origin is a group-room member session: the
304
+ * report goes to that member session instead of the agent's home. */
305
+ const roomFrom = (originSessionId) => (roomMemberOfSession(originSessionId) ? { from: originSessionId } : {});
306
+
307
+ /** The route of `modelRef`: a provider/model[@effort…] ref (a superseded
308
+ * model runs on its successor) or a preset name. */
309
+ async function configModelRoute(modelRef) {
310
+ const [{ modelRouteFields, parseScheduleModelRef }, { migrateCodexRoute }, { loadConfig }] = await Promise.all([
311
+ import('../runtime/shared/schedule-model-ref.mjs'),
312
+ import('../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs'),
313
+ import('../runtime/agent/orchestrator/config.mjs'),
314
+ ]);
315
+ const ref = parseScheduleModelRef(modelRef);
316
+ if (ref && typeof ref === 'object') return modelRouteFields(migrateCodexRoute(ref));
317
+ const preset = loadConfig({ secrets: false }).presets?.find((p) => p.id === ref || p.name === ref);
318
+ if (!preset) throw new Error(`model "${ref}" is neither a provider/model route nor a known preset name`);
319
+ return modelRouteFields(preset);
320
+ }
321
+
322
+ /** The Lead (Main) route of the config — what a desktop New task starts on.
323
+ * Null while none is chosen. */
324
+ async function configLeadRoute() {
325
+ const [{ modelRouteFields }, { resolveRoute }, { loadConfig }] = await Promise.all([
326
+ import('../runtime/shared/schedule-model-ref.mjs'),
327
+ import('./boot/shared.mjs'),
328
+ import('../runtime/agent/orchestrator/config.mjs'),
329
+ ]);
330
+ const route = resolveRoute(loadConfig({ secrets: false }), {});
331
+ return clean(route?.provider) && clean(route?.model) ? modelRouteFields(route) : null;
332
+ }
333
+
334
+ /** The model the user last used in a coding session (the runtime's stand-in
335
+ * for the desktop's last New-task model): the first row that has one in the
336
+ * catalog, which lists newest activity first. Pages on (bounded) until a
337
+ * row qualifies; null when none does. */
338
+ async function lastUsedSessionRoute(sessionService) {
339
+ let cursor = null;
340
+ for (let page = 0; page < LAST_USED_ROUTE_MAX_PAGES; page += 1) {
341
+ const listed = await sessionService.listSessionCatalog({ page: { limit: 50, cursor } });
342
+ const used = (Array.isArray(listed?.sessions) ? listed.sessions : []).find(
343
+ (row) => row?.sessionMode !== 'agent' && clean(row?.provider) && clean(row?.model)
344
+ );
345
+ if (used) return { provider: clean(used.provider), model: clean(used.model) };
346
+ cursor = listed?.nextCursor || null;
347
+ if (!cursor) return null;
348
+ }
349
+ return null;
350
+ }
351
+
352
+ /** Wait for the final answer of the task turn `runId` of `sessionId` ended
353
+ * completed: the end of the latest turn since it (a Lead that handed work
354
+ * to agents ends its turn and answers in the turn their results wake),
355
+ * once the session is idle with no pending agent work (pendingWork) for
356
+ * `settleMs`. `immediate` is runId's own end, when this process saw it.
357
+ * `finish(status, text)` runs once; `check` re-evaluates (call it on every
358
+ * state change of `runtime`), `stop` ends the wait. */
359
+ function followTaskToFinal(
360
+ { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
361
+ { sessionId, runId, runtime, immediate = null, wakeOwed = false, finish, progress = null }
362
+ ) {
363
+ let progressed = false;
364
+ let pendingSince = 0;
365
+ // Agent work still pending `pendingCapMs` after the chain's latest turn
366
+ // ended: `progress(text)` reports that answer once, marked still in
367
+ // progress, and the wait for the final one goes on.
368
+ async function reportProgress() {
369
+ if (progressed || typeof progress !== 'function') return;
370
+ const latest = latestTurnOutcome((await loadSession(sessionId))?.messages, runId);
371
+ let end = latest?.outcome && latest.outcome !== 'open' ? latest.outcome : null;
372
+ if (immediate && (!latest || latest.id === runId)) end = immediate;
373
+ if (!end) return;
374
+ pendingSince ||= Date.now();
375
+ if (Date.now() - (latest?.at || pendingSince) < pendingCapMs) return;
376
+ progressed = true;
377
+ await progress(`${STILL_WORKING_TEXT}\n\n${end.text}`);
378
+ }
379
+ let done = false;
380
+ let checking = false;
381
+ let again = false;
382
+ let candidate = '';
383
+ let candidateAt = 0;
384
+ let timer = null;
385
+ let wakes = 0;
386
+ let wokeAt = 0;
387
+ const unwoken = (why) => {
388
+ log(`${sessionId} could not take its agents' results after the restart: ${why}`);
389
+ return { key: 'unwoken', status: 'failed', text: RESUME_FAILED_TEXT, now: true };
390
+ };
391
+ // `wakeOwed` (a restarted watch): deliver the agent results queued for
392
+ // the idle session through the notification bus so a turn takes them, at
393
+ // most OWED_WAKE_TRIES times, a poll apart; null while that turn is owed.
394
+ async function wakeForOwed() {
395
+ const owed = await owedResults(sessionId);
396
+ if (!owed.length) return undefined;
397
+ if (Date.now() - wokeAt < pollMs) return null;
398
+ if (wakes >= OWED_WAKE_TRIES) return unwoken(`still not taken after ${wakes} deliveries`);
399
+ wakes += 1;
400
+ wokeAt = Date.now();
401
+ let delivered = false;
402
+ try {
403
+ if (typeof deliverCompletion === 'function') {
404
+ const results = await Promise.all(owed.map(({ text, meta }) => deliverCompletion(sessionId, text, meta)));
405
+ delivered = results.some((result) => result === true);
406
+ }
407
+ } catch (error) {
408
+ return unwoken(error?.message || error);
409
+ }
410
+ return delivered ? null : unwoken('no session runtime took the delivery');
411
+ }
412
+ const arm = (ms) => {
413
+ clearTimeout(timer);
414
+ timer = setTimeout(() => void check().catch(failed), ms);
415
+ timer.unref?.();
416
+ };
417
+ const stop = () => {
418
+ done = true;
419
+ clearTimeout(timer);
420
+ };
421
+ const failed = (error) => log(`following ${sessionId} to its final answer failed: ${error?.message || error}`);
422
+ // The end to report now, or null while the task still works.
423
+ async function ready() {
424
+ if (runtime?.getState?.()?.busy) return null;
425
+ if (wakeOwed) {
426
+ const woke = await wakeForOwed();
427
+ if (woke !== undefined) return woke;
428
+ }
429
+ if (await pendingWork(sessionId)) {
430
+ await reportProgress();
431
+ return null;
432
+ }
433
+ const stored = await loadSession(sessionId);
434
+ if (!stored && !immediate) return { key: 'gone', status: 'cancelled', text: GONE_TEXT };
435
+ const latest = latestTurnOutcome(stored?.messages, runId);
436
+ // Provenance the chain needs is missing: its answer may not be final.
437
+ if (latest?.ambiguous) {
438
+ await reportProgress();
439
+ return null;
440
+ }
441
+ if (immediate && (!latest || latest.id === runId)) return { key: runId, ...immediate };
442
+ if (!latest?.outcome || latest.outcome === 'open') return null;
443
+ return { key: `${latest.id}:${latest.outcome.status}`, ...latest.outcome };
444
+ }
445
+ async function check() {
446
+ if (done) return;
447
+ if (checking) {
448
+ again = true;
449
+ return;
450
+ }
451
+ checking = true;
452
+ try {
453
+ do {
454
+ again = false;
455
+ const end = await ready();
456
+ if (done) return;
457
+ if (!end) {
458
+ candidate = '';
459
+ arm(pollMs);
460
+ } else if (!end.now && settleMs > 0 && (end.key !== candidate || Date.now() - candidateAt < settleMs)) {
461
+ // Final only if it is still the answer after the quiet period.
462
+ if (end.key !== candidate) {
463
+ candidate = end.key;
464
+ candidateAt = Date.now();
465
+ }
466
+ arm(candidateAt + settleMs - Date.now());
467
+ } else {
468
+ stop();
469
+ await finish(end.status, end.text);
470
+ }
471
+ } while (again && !done);
472
+ } finally {
473
+ checking = false;
474
+ }
475
+ }
476
+ return { check: () => check().catch(failed), stop };
477
+ }
478
+
479
+ /** Store the durable watch of turn `id` before it can start, so no end goes
480
+ * unreported across a restart; a turn whose watch could not be stored runs
481
+ * without a report (reportsBack false) rather than claim one. `noReport`:
482
+ * why the turn will not report back, told to the bot (reportsBackReason). */
483
+ async function watchTurn({ watches, owner, log }, sessionId, origin, id) {
484
+ if (origin.notify === false) return { notify: false, noReport: NOTIFY_OFF_REASON };
485
+ try {
486
+ await watches.add({
487
+ runId: id,
488
+ agentId: origin.agentId,
489
+ homeSessionId: origin.sessionId,
490
+ taskSessionId: sessionId,
491
+ title: origin.title,
492
+ ...(origin.target === 'session' ? { target: 'session' } : {}),
493
+ ...(origin.textOnly ? { textOnly: true } : {}),
494
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
495
+ owner,
496
+ at: Date.now(),
497
+ });
498
+ return { notify: true, noReport: '' };
499
+ } catch (error) {
500
+ log(`watch of ${sessionId} failed, sending without a report: ${error?.message || error}`);
501
+ return {
502
+ notify: false,
503
+ noReport:
504
+ error?.code === 'REPORT_WATCH_CAP'
505
+ ? WATCH_CAP_REASON
506
+ : `its report could not be recorded (${error?.message || error}); check that session for the result`,
507
+ };
508
+ }
509
+ }
510
+
511
+ /** Deliver report `payload` of task session `sessionId` to agent `agentId`,
512
+ * retrying after each of `retryDelaysMs`; the last failure throws. */
513
+ async function deliverWithRetries({ deliver, retryDelaysMs, log }, agentId, sessionId, payload) {
514
+ for (let attempt = 0; ; attempt += 1) {
515
+ try {
516
+ return await deliver(agentId, payload);
517
+ } catch (error) {
518
+ if (attempt >= retryDelaysMs.length) throw error;
519
+ log(`report of ${sessionId} failed (try ${attempt + 1}), retrying: ${error?.message || error}`);
520
+ await new Promise((resolveWait) => setTimeout(resolveWait, retryDelaysMs[attempt]));
521
+ }
522
+ }
523
+ }
524
+
525
+ /** Where create's `worktree` option starts the task session: `{ mode }` is
526
+ * 'default' (the user's New task setting), 'branch' (the Project folder on its
527
+ * current branch) or 'worktree' (its own new one, `name` optional). */
528
+ function taskLocation(option) {
529
+ if (option === undefined || option === null) return { mode: 'default' };
530
+ if (typeof option !== 'object' || Array.isArray(option) || !['branch', 'worktree'].includes(option.mode)) {
531
+ throw new Error('session: worktree.mode must be "branch" or "worktree"');
532
+ }
533
+ if (option.name === undefined) return { mode: option.mode };
534
+ if (option.mode !== 'worktree') throw new Error('session: worktree.name applies only to mode "worktree"');
535
+ const reason = worktreeNameError(option.name);
536
+ if (reason) throw new Error(`session: worktree.name is invalid: ${reason}`);
537
+ return { mode: 'worktree', name: option.name };
538
+ }
539
+
540
+ const megabytes = (bytes) => `${Math.round(Number(bytes) / (1024 * 1024))} MB`;
541
+
542
+ /** A typed worktree failure as the model should read it: its code and, for a
543
+ * large repository, the size to put to the user. */
544
+ function taskWorktreeFailure(error) {
545
+ const details = error?.details;
546
+ const size =
547
+ error?.code === 'WORKTREE_LARGE_REPO_CONFIRM' && Number.isFinite(details?.estimateBytes)
548
+ ? ` The repository is about ${megabytes(details.estimateBytes)} (warning threshold ${megabytes(details.thresholdBytes)}). Ask the user whether to create the worktree anyway; only if they confirm, call create again with confirm_large: true.`
549
+ : '';
550
+ return Object.assign(new Error(`session: ${error.code}: ${error.message}${size}`), {
551
+ code: error.code,
552
+ ...(details ? { details } : {}),
553
+ });
554
+ }
555
+
556
+ /** Start task session `sessionId` in its own worktree and return its workspace
557
+ * value. A New task never carries the Project folder's changes. Unless
558
+ * `explicit`, a repo that cannot have one keeps the Project folder (null).
559
+ * The session must end up bound: the brief goes out only after it. */
560
+ async function requestTaskWorktree(sessions, sessionId, { title, requestId, name, confirmLarge, explicit }) {
561
+ try {
562
+ const result = await sessions.configureSession(
563
+ {
564
+ sessionId,
565
+ action: 'requestWorktree',
566
+ args: [
567
+ {
568
+ provision: true,
569
+ carryChanges: 'leave',
570
+ prompt: title,
571
+ txId: requestId,
572
+ ...(name ? { name } : {}),
573
+ ...(confirmLarge ? { confirmLarge: true } : {}),
574
+ },
575
+ ],
576
+ },
577
+ { revision: 1 }
578
+ );
579
+ const value = result?.value;
580
+ if (value?.pending !== false || typeof value.workspace?.worktreeId !== 'string') {
581
+ throw worktreeError('WORKTREE_NOT_IMPLEMENTED', 'The session did not enter its worktree before the brief.');
582
+ }
583
+ return value;
584
+ } catch (error) {
585
+ if (!explicit && error?.code === 'WORKTREE_UNSUPPORTED_REPO') return null;
586
+ throw typeof error?.code === 'string' && error.code.startsWith('WORKTREE_') ? taskWorktreeFailure(error) : error;
587
+ }
588
+ }
589
+
590
+ /** The origin a `send` of agent `agentId` (from its home or room member
591
+ * session `callerSessionId`) reports to: the stored one of a task session
592
+ * it created, else one for this send only — never stored on the target,
593
+ * whose own origin and owner stay as they are. Either reports to the
594
+ * conversation the send came from. Null for a caller that is no agent
595
+ * conversation. */
596
+ async function sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId) {
597
+ if (!agentId) return null;
598
+ const target = await loadSession(sessionId);
599
+ const origin = target?.taskOrigin;
600
+ if (origin?.agentId === agentId) {
601
+ return callerSessionId && callerSessionId !== origin.sessionId ? { ...origin, sessionId: callerSessionId } : origin;
602
+ }
603
+ const title = (clean(target?.title) || sessionId).slice(0, TITLE_LIMIT);
604
+ // Files come back only from sessions the agent created (above) or its own
605
+ // room member threads; any other session reports its text alone.
606
+ const own = roomMemberOfSession(sessionId)?.agentId === agentId;
607
+ return {
608
+ agentId,
609
+ sessionId: callerSessionId,
610
+ title,
611
+ notify: true,
612
+ target: 'session',
613
+ ...(own ? {} : { textOnly: true, envelopeName: await agentName(agentId) }),
614
+ };
615
+ }
616
+
617
+ /** Re-attach one watch a stopped daemon left: report the turn's end once
618
+ * from the target's record, or watch it until it ends. */
619
+ async function rearmTaskWatch(
620
+ { deliver, loadSession, materializeSession, queuedSubmissionIds, artifactFields, followToFinal, log },
621
+ watch
622
+ ) {
623
+ const sessionId = watch.taskSessionId;
624
+ // The turn that answers: the watched submission's own, or the one it
625
+ // follows (a room member's turn under another submission id).
626
+ const turnId = watch.follows || watch.runId;
627
+ const finish = async (status, text = '') =>
628
+ deliver(watch.agentId, {
629
+ runId: `${watch.runId}_end`,
630
+ taskSessionId: sessionId,
631
+ title: watch.title,
632
+ status,
633
+ text,
634
+ ...(watch.target === 'session' ? { target: 'session' } : {}),
635
+ ...(watch.hop > 0 ? { hop: watch.hop } : {}),
636
+ ...roomFrom(watch.homeSessionId),
637
+ watchId: watch.runId,
638
+ ...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT
639
+ ? {}
640
+ : await artifactFields(sessionId, turnId)),
641
+ });
642
+ if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
643
+ let runtime;
644
+ try {
645
+ runtime = await materializeSession(sessionId);
646
+ } catch (error) {
647
+ // It can never take its agents' results: say so once instead of waiting.
648
+ log(`${sessionId} could not be loaded after the restart: ${error?.message || error}`);
649
+ return finish('failed', RESUME_FAILED_TEXT);
650
+ }
651
+ let done = false;
652
+ let checking = false;
653
+ let again = false;
654
+ let unsubscribe = () => {};
655
+ // Set once the watched turn is found ended completed: the report then
656
+ // waits for the task's final answer, as in the process that sent it.
657
+ let follow = null;
658
+ const check = async () => {
659
+ if (done) return;
660
+ if (follow) return follow.check();
661
+ if (checking) {
662
+ again = true;
663
+ return;
664
+ }
665
+ checking = true;
666
+ try {
667
+ do {
668
+ again = false;
669
+ const outcome = submissionOutcome((await loadSession(sessionId))?.messages, turnId);
670
+ const state = runtime?.getState?.() || {};
671
+ let end = outcome && outcome !== 'open' ? outcome : null;
672
+ if (!end && !state.busy) {
673
+ const queued = (state.queued || []).some((entry) => entry?.id === turnId);
674
+ if (outcome === 'open' && !queued) end = { status: 'cancelled', text: '' };
675
+ else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(turnId)) {
676
+ end = { status: 'cancelled', text: LOST_TEXT };
677
+ }
678
+ }
679
+ if (end?.status === 'completed') {
680
+ follow = followToFinal({
681
+ sessionId,
682
+ runId: turnId,
683
+ runtime,
684
+ wakeOwed: true,
685
+ progress: (text) =>
686
+ deliver(watch.agentId, {
687
+ runId: `${watch.runId}_progress`,
688
+ taskSessionId: sessionId,
689
+ title: watch.title,
690
+ status: 'working',
691
+ text,
692
+ ...(watch.target === 'session' ? { target: 'session' } : {}),
693
+ ...(watch.hop > 0 ? { hop: watch.hop } : {}),
694
+ ...roomFrom(watch.homeSessionId),
695
+ }),
696
+ finish: async (status, text) => {
697
+ done = true;
698
+ unsubscribe();
699
+ await finish(status, text);
700
+ },
701
+ });
702
+ } else if (end) {
703
+ done = true;
704
+ unsubscribe();
705
+ await finish(end.status, end.text);
706
+ }
707
+ } while (again && !done && !follow);
708
+ } finally {
709
+ checking = false;
710
+ }
711
+ if (follow) await follow.check();
712
+ };
713
+ const failed = (error) => log(`re-attaching ${sessionId} failed: ${error?.message || error}`);
714
+ unsubscribe = runtime?.subscribe?.(() => void check().catch(failed)) || unsubscribe;
715
+ await check();
716
+ }
717
+
114
718
  export function createAgentTaskSessions({
115
719
  getSessionService,
116
720
  deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
@@ -119,6 +723,8 @@ export function createAgentTaskSessions({
119
723
  add: async (watch) => (await import('./agent-home-delivery.mjs')).watchAgentTask(watch),
120
724
  list: async () => (await import('./agent-home-delivery.mjs')).agentTaskWatches(),
121
725
  remove: async (runId) => (await import('./agent-home-delivery.mjs')).unwatchAgentTask(runId),
726
+ follow: async (runId, submissionId) =>
727
+ (await import('./agent-home-delivery.mjs')).followAgentTask(runId, submissionId),
122
728
  },
123
729
  // Submissions still queued (durably) for a session after a restart.
124
730
  queuedSubmissionIds = async (sessionId) =>
@@ -130,48 +736,11 @@ export function createAgentTaskSessions({
130
736
  listProjects = async () => (await import('../runtime/shared/projects.mjs')).listProjects(),
131
737
  newTaskDefault = async () =>
132
738
  (await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).worktree?.newTaskDefault,
133
- modelRoute = async (modelRef) => {
134
- const [{ modelRouteFields, parseScheduleModelRef }, { migrateCodexRoute }, { loadConfig }] = await Promise.all([
135
- import('../runtime/shared/schedule-model-ref.mjs'),
136
- import('../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs'),
137
- import('../runtime/agent/orchestrator/config.mjs'),
138
- ]);
139
- // A provider/model[@effort…] ref (a superseded model runs on its
140
- // successor) or a preset name.
141
- const ref = parseScheduleModelRef(modelRef);
142
- if (ref && typeof ref === 'object') return modelRouteFields(migrateCodexRoute(ref));
143
- const preset = loadConfig({ secrets: false }).presets?.find((p) => p.id === ref || p.name === ref);
144
- if (!preset) throw new Error(`model "${ref}" is neither a provider/model route nor a known preset name`);
145
- return modelRouteFields(preset);
146
- },
739
+ modelRoute = configModelRoute,
147
740
  // The Lead (Main) route of the config — what a desktop New task starts on.
148
- // Null while none is chosen.
149
- leadRoute = async () => {
150
- const [{ modelRouteFields }, { resolveRoute }, { loadConfig }] = await Promise.all([
151
- import('../runtime/shared/schedule-model-ref.mjs'),
152
- import('./boot/shared.mjs'),
153
- import('../runtime/agent/orchestrator/config.mjs'),
154
- ]);
155
- const route = resolveRoute(loadConfig({ secrets: false }), {});
156
- return clean(route?.provider) && clean(route?.model) ? modelRouteFields(route) : null;
157
- },
158
- // The model the user last used in a coding session (the runtime's stand-in
159
- // for the desktop's last New-task model): the first row that has one in the
160
- // catalog, which lists newest activity first. Pages on (bounded) until a
161
- // row qualifies; null when none does.
162
- lastUsedRoute = async (sessionService) => {
163
- let cursor = null;
164
- for (let page = 0; page < LAST_USED_ROUTE_MAX_PAGES; page += 1) {
165
- const listed = await sessionService.listSessionCatalog({ page: { limit: 50, cursor } });
166
- const used = (Array.isArray(listed?.sessions) ? listed.sessions : []).find(
167
- (row) => row?.sessionMode !== 'agent' && clean(row?.provider) && clean(row?.model)
168
- );
169
- if (used) return { provider: clean(used.provider), model: clean(used.model) };
170
- cursor = listed?.nextCursor || null;
171
- if (!cursor) return null;
172
- }
173
- return null;
174
- },
741
+ leadRoute = configLeadRoute,
742
+ // The model the user last used in a coding session.
743
+ lastUsedRoute = lastUsedSessionRoute,
175
744
  // The files turn `runId` of `sessionId` produced (handoff-attachments.mjs),
176
745
  // read from its live runtime when one is given, else its stored record.
177
746
  turnArtifacts = async (sessionId, runId, runtime = null) => {
@@ -180,6 +749,27 @@ export function createAgentTaskSessions({
180
749
  const messages = turnSlice(Array.isArray(live?.messages) ? live.messages : stored?.messages, { runId });
181
750
  return collectTurnArtifacts(messages, { cwd: stored?.cwd, roots: sessionRoots(stored) });
182
751
  },
752
+ // The name a bot goes by in the envelope of its messages to other sessions.
753
+ agentName = async (agentId) =>
754
+ (await import('../runtime/agent/orchestrator/context/role-instructions.mjs')).agentDisplayName(agentId),
755
+ // Waits before each in-process retry of a report that could not be queued.
756
+ retryDelaysMs = REPORT_RETRY_DELAYS_MS,
757
+ // Whether a task session still waits on agent work it handed off; its
758
+ // turn's end then is not the final answer yet.
759
+ pendingWork = taskSessionPendingWork,
760
+ settleMs = FINAL_SETTLE_MS,
761
+ pollMs = FINAL_POLL_MS,
762
+ // After a restart: the agent results queued for a task session, and the
763
+ // notification-bus delivery (the session runtime host's
764
+ // notifySessionCompletion) that makes it take them in a turn — the restart
765
+ // left nothing to wake it. `settleAgentResults` first turns the agents the
766
+ // restart killed into queued failure results (agent-tool restart-notify).
767
+ owedResults = owedAgentResults,
768
+ deliverCompletion = null,
769
+ settleAgentResults = async () => {},
770
+ // Pending agent work this long after the latest answer reports that answer
771
+ // as still in progress (once); the final report still follows.
772
+ pendingCapMs = PENDING_CAP_MS,
183
773
  log = (line) => process.stderr.write(`mixdog task sessions: ${line}\n`),
184
774
  }) {
185
775
  /** The report fields of turn `runId`'s files; none when it produced none. */
@@ -200,55 +790,51 @@ export function createAgentTaskSessions({
200
790
  return value;
201
791
  };
202
792
 
793
+ const followToFinal = (follow) =>
794
+ followTaskToFinal(
795
+ { loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
796
+ follow
797
+ );
798
+
203
799
  /** Submit `prompt` as the origin agent's turn; its end (and a wait for the
204
- * user in it) reports to the agent's home. Resolves on intake. */
205
- async function agentTurn(sessionId, prompt, origin, id = `atask_${randomUUID()}`, beforeReport = null) {
800
+ * user in it) reports to the agent's home. Resolves on intake. `submit`
801
+ * takes the turn in another way than a prompt to `sessionId` (a message
802
+ * into a group room, whose member answers in `sessionId`): given `{ id,
803
+ * onSettled }`, it resolves `{ accepted }`. */
804
+ async function agentTurn(sessionId, prompt, origin, id = `atask_${randomUUID()}`, beforeReport = null, submit = null) {
206
805
  const sessions = service();
207
806
  const target = origin.target === 'session' ? { target: 'session' } : {};
208
- // The watch is durable before the turn can start, so no end goes
209
- // unreported across a restart; a turn whose watch could not be stored
210
- // runs without a report (reportsBack false) rather than claim one.
211
- let notify = false;
212
- if (origin.notify !== false) {
213
- try {
214
- await watches.add({
215
- runId: id,
216
- agentId: origin.agentId,
217
- homeSessionId: origin.sessionId,
218
- taskSessionId: sessionId,
219
- title: origin.title,
220
- ...target,
221
- ...(origin.textOnly ? { textOnly: true } : {}),
222
- owner: processToken,
223
- at: Date.now(),
224
- });
225
- notify = true;
226
- } catch (error) {
227
- log(`watch of ${sessionId} failed, sending without a report: ${error?.message || error}`);
228
- }
229
- }
807
+ const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
230
808
  const dropWatch = () =>
231
809
  watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
232
810
  let runtime = null;
811
+ // A report that cannot be queued is retried in this process; when every try
812
+ // fails the durable watch stays and restart recovery delivers it (the queue
813
+ // dedupes by runId, so a retry never posts twice).
233
814
  const report = (status, text = '') =>
234
815
  Promise.resolve(beforeReport)
235
816
  .then(async () =>
236
- deliver(origin.agentId, {
237
- runId: `${id}_${status === 'blocked' ? 'blocked' : 'end'}`,
817
+ deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
818
+ // 'working': a still-in-progress answer (followToFinal), not the end.
819
+ runId: `${id}_${{ blocked: 'blocked', working: 'progress' }[status] || 'end'}`,
238
820
  taskSessionId: sessionId,
239
821
  title: origin.title,
240
822
  status,
241
823
  text,
242
824
  ...target,
825
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
826
+ ...roomFrom(origin.sessionId),
243
827
  // A turn's end brings back the files it produced, when the agent
244
828
  // may receive them (reportOrigin `textOnly`).
245
- ...(status === 'blocked'
829
+ ...(status === 'blocked' || status === 'working'
246
830
  ? {}
247
831
  : { watchId: id, ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)) }),
248
832
  })
249
833
  )
250
834
  .catch((error) => log(`report of ${sessionId} failed: ${error?.message || error}`));
251
835
  let unwatch = () => {};
836
+ // Set once the turn ended completed: the report waits for the final answer.
837
+ let follow = null;
252
838
  let result;
253
839
  try {
254
840
  if (notify) {
@@ -256,27 +842,60 @@ export function createAgentTaskSessions({
256
842
  let blocked = false;
257
843
  unwatch =
258
844
  runtime?.subscribe?.(() => {
845
+ follow?.check();
259
846
  if (blocked || !runtime.getState?.()?.toolApproval) return;
260
847
  blocked = true;
261
848
  void report('blocked');
262
849
  }) || unwatch;
263
850
  }
264
- result = await sessions.submitSession({
265
- sessionId,
266
- prompt,
267
- options: {
268
- id,
269
- mode: 'prompt',
270
- transcriptMeta: { sender: 'lead' },
271
- ...(notify
272
- ? {
273
- onSettled: (detail) => {
274
- unwatch();
275
- void report(turnStatus(detail), detail?.result?.content || detail?.error || '');
276
- },
277
- }
278
- : {}),
851
+ // A bot's send to a session that is not its own task session reaches the
852
+ // model inside the sender envelope (`envelopeName`, set by reportOrigin).
853
+ const sent = origin.envelopeName
854
+ ? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
855
+ : prompt;
856
+ const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
857
+ result = await take({
858
+ id,
859
+ mode: 'prompt',
860
+ // `fromAgent`: the sending bot, recorded when it writes into another
861
+ // bot's home chat (otherwise the row would read as the user's);
862
+ // `hop`: its place in a bot-to-bot chain (bot-hops.mjs).
863
+ transcriptMeta: {
864
+ sender: 'lead',
865
+ ...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
866
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
279
867
  },
868
+ ...(notify
869
+ ? {
870
+ // A room answers this send in a turn under another submission
871
+ // id (one turn for several sends, or a fresh turn for one cut
872
+ // short): the watch follows that turn, recorded before it runs.
873
+ follow: (submissionId) => watches.follow?.(id, submissionId),
874
+ onSettled: (detail) => {
875
+ const status = turnStatus(detail);
876
+ const text = detail?.result?.content || detail?.error || '';
877
+ // A failed or cancelled turn reports at once; a completed one
878
+ // reports the task's final answer (followToFinal).
879
+ if (status !== 'completed') {
880
+ unwatch();
881
+ void report(status, text);
882
+ return;
883
+ }
884
+ follow = followToFinal({
885
+ sessionId,
886
+ runId: id,
887
+ runtime,
888
+ immediate: { status, text },
889
+ finish: async (endStatus, endText) => {
890
+ unwatch();
891
+ await report(endStatus, endText);
892
+ },
893
+ progress: (progressText) => report('working', progressText),
894
+ });
895
+ follow.check();
896
+ },
897
+ }
898
+ : {}),
280
899
  });
281
900
  } catch (error) {
282
901
  unwatch();
@@ -288,71 +907,38 @@ export function createAgentTaskSessions({
288
907
  unwatch();
289
908
  if (notify) await dropWatch();
290
909
  }
291
- return { accepted, reportsBack: accepted && notify };
292
- }
293
-
294
- /** Re-attach one watch a stopped daemon left: report the turn's end once
295
- * from the target's record, or watch it until it ends. */
296
- async function rearm(watch) {
297
- const sessionId = watch.taskSessionId;
298
- const finish = async (status, text = '') =>
299
- deliver(watch.agentId, {
300
- runId: `${watch.runId}_end`,
301
- taskSessionId: sessionId,
302
- title: watch.title,
303
- status,
304
- text,
305
- ...(watch.target === 'session' ? { target: 'session' } : {}),
306
- watchId: watch.runId,
307
- ...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT
308
- ? {}
309
- : await artifactFields(sessionId, watch.runId)),
310
- });
311
- if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
312
- const runtime = await service().materializeSession(sessionId);
313
- let done = false;
314
- let checking = false;
315
- let again = false;
316
- let unsubscribe = () => {};
317
- const check = async () => {
318
- if (done) return;
319
- if (checking) {
320
- again = true;
321
- return;
322
- }
323
- checking = true;
324
- try {
325
- do {
326
- again = false;
327
- const outcome = submissionOutcome((await loadSession(sessionId))?.messages, watch.runId);
328
- const state = runtime?.getState?.() || {};
329
- let end = outcome && outcome !== 'open' ? outcome : null;
330
- if (!end && !state.busy) {
331
- const queued = (state.queued || []).some((entry) => entry?.id === watch.runId);
332
- if (outcome === 'open' && !queued) end = { status: 'cancelled', text: '' };
333
- else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(watch.runId)) {
334
- end = { status: 'cancelled', text: LOST_TEXT };
335
- }
336
- }
337
- if (end) {
338
- done = true;
339
- unsubscribe();
340
- await finish(end.status, end.text);
341
- }
342
- } while (again && !done);
343
- } finally {
344
- checking = false;
345
- }
910
+ return {
911
+ accepted,
912
+ reportsBack: accepted && notify,
913
+ ...(accepted && !notify ? { reportsBackReason: noReport } : {}),
346
914
  };
347
- const failed = (error) => log(`re-attaching ${sessionId} failed: ${error?.message || error}`);
348
- unsubscribe = runtime?.subscribe?.(() => void check().catch(failed)) || unsubscribe;
349
- await check();
350
915
  }
351
916
 
917
+ const rearm = (watch) =>
918
+ rearmTaskWatch(
919
+ {
920
+ deliver,
921
+ loadSession,
922
+ materializeSession: (sessionId) => service().materializeSession(sessionId),
923
+ queuedSubmissionIds,
924
+ artifactFields,
925
+ followToFinal,
926
+ log,
927
+ },
928
+ watch
929
+ );
930
+
352
931
  /** Daemon start: re-attach every reporting turn an earlier process watched.
353
932
  * Resolves once each is reported or watched. */
354
933
  async function recover() {
355
934
  const pending = (await watches.list()).filter((watch) => watch?.owner !== processToken);
935
+ // The agents the restart killed become queued failure results first, so
936
+ // each re-attached task session has them to take (followToFinal wakeOwed).
937
+ if (pending.length) {
938
+ await Promise.resolve()
939
+ .then(settleAgentResults)
940
+ .catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
941
+ }
356
942
  await Promise.all(
357
943
  pending.map((watch) =>
358
944
  rearm(watch).catch((error) => log(`re-attaching ${watch?.taskSessionId} failed: ${error?.message || error}`))
@@ -361,12 +947,31 @@ export function createAgentTaskSessions({
361
947
  return pending.length;
362
948
  }
363
949
 
364
- /** Create the task session for `brief` and start it; returns at once. */
365
- async function create(args = {}, { callerSessionId, callerAgentId, callerCwd, attachmentParts = [] } = {}) {
950
+ /** Create the task session for `brief` and start it; returns at once.
951
+ * `callerScope` is the caller's trusted worktree scope (worktree-access.mjs
952
+ * worktreeCallerScope): provisioning a worktree is a mutation only an
953
+ * attended conversation may make, so an explicit worktree from any other
954
+ * caller is refused and the New task default keeps it in the Project folder. */
955
+ async function create(
956
+ args = {},
957
+ { callerSessionId, callerAgentId, callerCwd, callerHop = 0, callerScope, attachmentParts = [] } = {}
958
+ ) {
366
959
  if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
367
960
  const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
368
961
  if (!brief) throw new Error('session: brief is required for action=create');
369
- const where = resolveTaskProject(args.project, { projects: await listProjects(), cwd: callerCwd || process.cwd() });
962
+ const location = taskLocation(args.worktree);
963
+ if (location.mode === 'worktree') assertWorktreeMutationAllowed(callerScope, 'session: create with a worktree');
964
+ const projects = await listProjects();
965
+ const where = resolveTaskProject(args.project, { projects, cwd: callerCwd || process.cwd() });
966
+ // An explicit worktree is never silently dropped: it needs a registered Project.
967
+ const registered = where.project && projects.some((entry) => samePath(entry.path, where.project.path));
968
+ if (location.mode === 'worktree' && !registered) {
969
+ throw taskWorktreeFailure(
970
+ worktreeError('WORKTREE_PROJECT_UNKNOWN', 'A worktree needs a registered Project; run list-projects.', {
971
+ projectPath: where.project?.path ?? null,
972
+ })
973
+ );
974
+ }
370
975
  const requestId = `atask_${randomUUID()}`;
371
976
  const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
372
977
  const taskOrigin = {
@@ -384,6 +989,16 @@ export function createAgentTaskSessions({
384
989
  : (await leadRoute()) || (await lastUsedRoute(service()));
385
990
  if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
386
991
  const sessions = service();
992
+ // Where it starts: the `worktree` option, else the user's New task default
993
+ // for a caller that may provision one. Decided before the session exists,
994
+ // so a failure here leaves nothing behind.
995
+ const explicit = location.mode === 'worktree';
996
+ const inWorktree =
997
+ explicit ||
998
+ (location.mode === 'default' &&
999
+ where.project &&
1000
+ callerScope?.attended === true &&
1001
+ (await newTaskDefault()) === 'worktree');
387
1002
  const created = await sessions.createSession({
388
1003
  ...route,
389
1004
  cwd: where.cwd,
@@ -392,19 +1007,42 @@ export function createAgentTaskSessions({
392
1007
  });
393
1008
  const sessionId = String(created?.sessionId || '');
394
1009
  if (!sessionId) throw new Error('session: task session creation returned no session id');
395
- // The user's New task default: a Project task starts in its own worktree.
396
- if (where.project && (await newTaskDefault()) === 'worktree') {
1010
+ if (inWorktree) {
397
1011
  try {
398
- await sessions.configureSession(
1012
+ await requestTaskWorktree(sessions, sessionId, {
1013
+ title,
1014
+ requestId,
1015
+ name: location.name,
1016
+ confirmLarge: args.confirm_large === true,
1017
+ explicit,
1018
+ });
1019
+ } catch (error) {
1020
+ // Nothing was sent: drop the empty session so a retry starts clean.
1021
+ let dropFailure = null;
1022
+ try {
1023
+ const dropped = await sessions.configureSession(
1024
+ { sessionId, action: 'deleteSession', args: [sessionId] },
1025
+ { revision: 1 }
1026
+ );
1027
+ // A resolved `false` is a refusal, not a deletion.
1028
+ if (dropped?.value === false) dropFailure = new Error('the session service refused the deletion');
1029
+ } catch (deleteError) {
1030
+ dropFailure = deleteError;
1031
+ }
1032
+ if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
1033
+ if (!dropFailure) throw error;
1034
+ // The model must know the empty session is still there to delete.
1035
+ throw Object.assign(
1036
+ new Error(
1037
+ `${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}.`
1038
+ ),
399
1039
  {
400
- sessionId,
401
- action: 'requestWorktree',
402
- args: [{ provision: true, carryChanges: 'leave', prompt: title, txId: requestId }],
403
- },
404
- { revision: 1 }
1040
+ ...(typeof error?.code === 'string' ? { code: error.code } : {}),
1041
+ ...(error?.details ? { details: error.details } : {}),
1042
+ orphanSessionId: sessionId,
1043
+ cause: error,
1044
+ }
405
1045
  );
406
- } catch (error) {
407
- if (error?.code !== 'WORKTREE_UNSUPPORTED_REPO') throw error;
408
1046
  }
409
1047
  }
410
1048
  // "Started" lands in the home before any report of the brief's turn.
@@ -414,7 +1052,13 @@ export function createAgentTaskSessions({
414
1052
  });
415
1053
  // The brief carries the files the agent forwarded (workspace-tools.mjs).
416
1054
  const prompt = handoffContent(brief, attachmentParts);
417
- const { accepted, reportsBack } = await agentTurn(sessionId, prompt, taskOrigin, requestId, ready);
1055
+ const { accepted, reportsBack, reportsBackReason } = await agentTurn(
1056
+ sessionId,
1057
+ prompt,
1058
+ callerHop > 0 ? { ...taskOrigin, hop: callerHop } : taskOrigin,
1059
+ requestId,
1060
+ ready
1061
+ );
418
1062
  if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
419
1063
  if (reportsBack) {
420
1064
  await deliver(callerAgentId, {
@@ -422,39 +1066,27 @@ export function createAgentTaskSessions({
422
1066
  taskSessionId: sessionId,
423
1067
  title,
424
1068
  status: 'started',
1069
+ ...roomFrom(callerSessionId),
425
1070
  }).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
426
1071
  }
427
1072
  started();
1073
+ // Started, not done: the final answer is reported later.
428
1074
  return {
429
1075
  session_id: sessionId,
1076
+ status: 'started',
430
1077
  title,
431
1078
  project: where.project?.path ?? null,
432
- ...(taskOrigin.notify && !reportsBack ? { reportsBack: false } : {}),
1079
+ ...(taskOrigin.notify && !reportsBack ? { reportsBack: false, reportsBackReason } : {}),
433
1080
  };
434
1081
  }
435
1082
 
436
- /** The origin a `send` of agent `agentId` (from its home `callerSessionId`)
437
- * reports to: the stored one of a task session it created, else one for
438
- * this send only — never stored on the target, whose own origin and owner
439
- * stay as they are. Null for a caller that is no agent conversation. */
440
- async function reportOrigin(sessionId, agentId, callerSessionId) {
441
- if (!agentId) return null;
442
- const target = await loadSession(sessionId);
443
- const origin = target?.taskOrigin;
444
- if (origin?.agentId === agentId) return origin;
445
- const title = (clean(target?.title) || sessionId).slice(0, TITLE_LIMIT);
446
- // Files come back only from sessions the agent created (above) or its own
447
- // room member threads; any other session reports its text alone.
448
- const own = roomMemberOfSession(sessionId)?.agentId === agentId;
449
- return {
450
- agentId,
451
- sessionId: callerSessionId,
452
- title,
453
- notify: true,
454
- target: 'session',
455
- ...(own ? {} : { textOnly: true }),
456
- };
457
- }
1083
+ const reportOrigin = (sessionId, agentId, callerSessionId) =>
1084
+ sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId);
458
1085
 
459
- return { create, reportOrigin, recover, send: (sessionId, prompt, origin) => agentTurn(sessionId, prompt, origin) };
1086
+ return {
1087
+ create,
1088
+ reportOrigin,
1089
+ recover,
1090
+ send: (sessionId, prompt, origin, submit = null) => agentTurn(sessionId, prompt, origin, undefined, null, submit),
1091
+ };
460
1092
  }