mixdog 1.3.1 → 1.3.2

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