mixdog 1.4.1 → 1.4.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 (326) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/gate-local.mjs +172 -61
  4. package/scripts/lib/gate-scheduler.mjs +138 -32
  5. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  6. package/scripts/lib/run-node-tests.mjs +54 -29
  7. package/scripts/lib/scoped-temp.mjs +1 -0
  8. package/scripts/lib/test-failure-records.mjs +14 -1
  9. package/scripts/lib/test-failure-summary.mjs +16 -4
  10. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  11. package/scripts/pptx-kit-index.mjs +5 -6
  12. package/scripts/suite-health.mjs +110 -28
  13. package/scripts/test.mjs +9 -14
  14. package/src/agents/advisor/AGENT.md +4 -4
  15. package/src/agents/advisor/agent.json +1 -1
  16. package/src/agents/explore/AGENT.md +22 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/cli.mjs +7 -0
  19. package/src/defaults/agents.json +7 -0
  20. package/src/defaults/shipped-history.json +18 -2
  21. package/src/defaults/skills/browser-use/SKILL.md +4 -1
  22. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  23. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  24. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  25. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  26. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  27. package/src/defaults/skills/computer-use/SKILL.md +12 -2
  28. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  29. package/src/defaults/skills/setup/references/actions.md +12 -6
  30. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  31. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  32. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  33. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  34. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  35. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  36. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  37. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  38. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  39. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  40. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  41. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  42. package/src/defaults/skills/visualize/SKILL.md +6 -2
  43. package/src/lib/rules-builder.cjs +6 -30
  44. package/src/rules/lead/LEAD.md +3 -3
  45. package/src/rules/shared/20-research.md +3 -6
  46. package/src/rules/shared/40-editing.md +1 -0
  47. package/src/rules/shared/60-verification.md +10 -9
  48. package/src/rules/shared/70-delivery.md +4 -3
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
  52. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  54. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  55. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
  56. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  57. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  58. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  59. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  60. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  61. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  63. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
  64. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  65. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
  66. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  69. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  74. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  75. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  78. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  79. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  80. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
  81. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  82. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  83. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  85. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
  88. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
  89. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
  93. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  94. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  95. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  96. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  97. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  98. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
  99. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  100. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  102. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  104. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  105. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
  106. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
  107. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
  108. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
  109. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
  110. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  111. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  112. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  116. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  117. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  118. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  123. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
  124. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
  125. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  126. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  127. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
  128. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
  129. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  130. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  131. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
  132. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
  133. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  134. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  135. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  136. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  137. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
  138. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
  139. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  143. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  144. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  145. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  147. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  148. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  154. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  155. package/src/runtime/channels/lib/whisper-port.mjs +19 -10
  156. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  157. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  158. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  159. package/src/runtime/explore/explorer.mjs +93 -0
  160. package/src/runtime/explore/tool-defs.mjs +35 -0
  161. package/src/runtime/explore/tool.mjs +123 -0
  162. package/src/runtime/memory/index.mjs +4 -0
  163. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  164. package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
  165. package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
  166. package/src/runtime/shared/agent-route-config.mjs +17 -2
  167. package/src/runtime/shared/atomic-file.mjs +103 -2
  168. package/src/runtime/shared/background-tasks.mjs +15 -6
  169. package/src/runtime/shared/connector-accounts.mjs +5 -5
  170. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  171. package/src/runtime/shared/connector-catalog.mjs +60 -52
  172. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  173. package/src/runtime/shared/file-has-data.mjs +26 -2
  174. package/src/runtime/shared/file-lock.mjs +13 -15
  175. package/src/runtime/shared/git-launch.d.mts +9 -0
  176. package/src/runtime/shared/git-launch.mjs +213 -0
  177. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  178. package/src/runtime/shared/llm/cost.mjs +6 -1
  179. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  180. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  181. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  182. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  183. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  184. package/src/runtime/shared/provider-accounts.mjs +52 -2
  185. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  186. package/src/runtime/shared/staged-update.mjs +47 -2
  187. package/src/runtime/shared/tool-surface.mjs +110 -36
  188. package/src/runtime/shared/tool-work-units.mjs +4 -0
  189. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  190. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  191. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  192. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  193. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  194. package/src/runtime/shared/worktrees/git.mjs +57 -0
  195. package/src/runtime/shared/worktrees/manager.mjs +205 -137
  196. package/src/runtime/tidy/install.mjs +95 -15
  197. package/src/runtime/tidy/languages.mjs +17 -15
  198. package/src/runtime/tidy/process.mjs +7 -4
  199. package/src/runtime/tidy/resolve.mjs +19 -16
  200. package/src/runtime/tidy/runners/biome.mjs +3 -3
  201. package/src/runtime/tidy/tool.mjs +1 -1
  202. package/src/runtime/tidy/worktree.mjs +10 -6
  203. package/src/runtime/visualize-bridge/client.mjs +4 -1
  204. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  205. package/src/runtime/web-search/index.mjs +1 -1
  206. package/src/session-runtime/agent-task-sessions.mjs +410 -353
  207. package/src/session-runtime/boot/tools.mjs +2 -0
  208. package/src/session-runtime/bot-status.mjs +103 -103
  209. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  210. package/src/session-runtime/conversation-import/discovery.mjs +159 -40
  211. package/src/session-runtime/conversation-import/service.mjs +9 -3
  212. package/src/session-runtime/handoff-attachments.mjs +1 -1
  213. package/src/session-runtime/held-requests.mjs +3 -1
  214. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  215. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  216. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
  217. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  218. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  219. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  220. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  221. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  222. package/src/session-runtime/provider-auth/login.mjs +2 -2
  223. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  224. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  225. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  226. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  227. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  228. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  229. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  230. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  231. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  232. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  233. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  234. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  235. package/src/session-runtime/services/provider-admin.mjs +281 -10
  236. package/src/session-runtime/session-turn-api.mjs +2 -0
  237. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  238. package/src/session-runtime/settings-system-api.mjs +1 -1
  239. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  240. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  241. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  242. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  243. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  244. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  245. package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
  246. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  247. package/src/session-runtime/workspace/session-view.mjs +149 -0
  248. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  249. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  250. package/src/session-runtime/workspace/transitions.mjs +108 -1066
  251. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  252. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  253. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  254. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  255. package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
  256. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  257. package/src/standalone/daemon-session-control.mjs +151 -136
  258. package/src/standalone/daemon.mjs +7 -0
  259. package/src/standalone/desktop-service-registry.mjs +9 -0
  260. package/src/standalone/local-session-runtime.mjs +5 -1
  261. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  262. package/src/standalone/session-client.mjs +5 -1
  263. package/src/standalone/session-frame-stream.mjs +83 -11
  264. package/src/standalone/session-protocol.mjs +4 -0
  265. package/src/standalone/session-service/agent-tree.mjs +45 -9
  266. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  267. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  268. package/src/standalone/session-service/stored-reader.mjs +127 -40
  269. package/src/tui/App.jsx +154 -164
  270. package/src/tui/app/app-view/floating-panel.jsx +3 -0
  271. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  272. package/src/tui/app/clipboard.mjs +21 -10
  273. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  274. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  275. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  276. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  277. package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
  278. package/src/tui/app/provider-setup-picker.mjs +20 -13
  279. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  280. package/src/tui/app/slash-commands.mjs +1 -0
  281. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  282. package/src/tui/app/slash-dispatch.mjs +1 -1
  283. package/src/tui/app/use-btw-panel.mjs +29 -0
  284. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  285. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  286. package/src/tui/app/use-mouse-input.mjs +11 -0
  287. package/src/tui/app/use-onboarding-start.mjs +8 -3
  288. package/src/tui/app/use-panel-transition.mjs +10 -1
  289. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  290. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  291. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  292. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  293. package/src/tui/app/use-selection-focus.mjs +11 -2
  294. package/src/tui/app/use-selection-geometry.mjs +18 -9
  295. package/src/tui/app/use-selection-paint.mjs +4 -4
  296. package/src/tui/app/use-selection-stitch.mjs +3 -3
  297. package/src/tui/app/use-slash-palette.mjs +62 -0
  298. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  299. package/src/tui/app/use-transcript-activity.mjs +47 -32
  300. package/src/tui/app/use-transcript-follow.mjs +12 -4
  301. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  302. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  303. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  304. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  305. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  306. package/src/tui/app/use-transcript-window.mjs +21 -11
  307. package/src/tui/app/use-tui-ready.mjs +18 -0
  308. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  309. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  310. package/src/tui/components/Picker.jsx +27 -3
  311. package/src/tui/components/SignInUrl.jsx +68 -0
  312. package/src/tui/components/TextEntryPanel.jsx +76 -46
  313. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  314. package/src/tui/dist/index.mjs +1896 -1341
  315. package/src/tui/session/goal-turn-state.mjs +6 -2
  316. package/src/tui/session/session-api/controls.mjs +4 -0
  317. package/src/tui/session/session-api/extensions.mjs +8 -0
  318. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  319. package/src/tui/session/session-api/intake.mjs +2 -0
  320. package/src/tui/session/session-api/integrations.mjs +2 -2
  321. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  322. package/src/tui/session/session-api/settings.mjs +2 -2
  323. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  324. package/src/tui/session/transcript-spill.mjs +92 -4
  325. package/src/tui/session/transcript-store.mjs +2 -0
  326. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -1094,23 +1094,392 @@ function startedGate() {
1094
1094
  return { ready, started };
1095
1095
  }
1096
1096
 
1097
+ /** The report delivery of send `id` of `origin` to task session `sessionId`.
1098
+ * `turn` (`{ runtime, sendIds }`) is what its turn knows when a report goes
1099
+ * out: the live runtime and the sends its end closes. A report that cannot
1100
+ * be queued is retried in this process; when every try fails the durable
1101
+ * watch stays and restart recovery delivers it (the queue dedupes by runId,
1102
+ * so a retry never posts twice). `answerId`: the turn whose answer a
1103
+ * progress update or the end carries. `deliverReport` rejects once every
1104
+ * try failed (a progress update is tried again at the follow's next check);
1105
+ * `report` logs it instead. */
1106
+ function sendReporter({ deliver, retryDelaysMs, artifactFields, log }, { sessionId, id, origin, beforeReport, turn }) {
1107
+ const target = origin.target === 'session' ? { target: 'session' } : {};
1108
+ const deliverReport = (status, text = '', { approvalId = '', answerId = '' } = {}) =>
1109
+ Promise.resolve(beforeReport).then(async () =>
1110
+ deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
1111
+ runId: turnReportRunId(sessionId, id, status, { approvalId, answerId }),
1112
+ taskSessionId: sessionId,
1113
+ title: origin.title,
1114
+ status,
1115
+ text,
1116
+ ...target,
1117
+ ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1118
+ ...roomFrom(origin.sessionId),
1119
+ // A progress update moves this send's durable mark to its turn
1120
+ // (agent-home-delivery.mjs enqueueTask), in the same write.
1121
+ ...(status === 'working' ? { progressWatchId: id, answerId } : {}),
1122
+ // A turn's end brings back the files it produced, when the agent
1123
+ // may receive them (reportOrigin `textOnly`).
1124
+ ...(status === 'blocked' || status === 'working'
1125
+ ? {}
1126
+ : {
1127
+ watchId: id,
1128
+ ...(turn.sendIds.length > 1 ? { watchIds: turn.sendIds } : {}),
1129
+ ...progressOf(id, answerId),
1130
+ ...(origin.textOnly ? {} : await artifactFields(sessionId, id, turn.runtime)),
1131
+ }),
1132
+ })
1133
+ );
1134
+ const report = (status, text = '', options = {}) =>
1135
+ deliverReport(status, text, options).catch((error) =>
1136
+ log(`report of ${sessionId} failed: ${error?.message || error}`)
1137
+ );
1138
+ return { deliverReport, report };
1139
+ }
1140
+
1141
+ /** The agent's work in `sessionId` for send `id`: owned from the send's
1142
+ * acceptance until it settles or is cancelled, whether or not it reports. A
1143
+ * send that holds no report watch keeps a durable record of the work instead
1144
+ * (`watches.own`), so a restart re-attaches it (recover); the writes of one
1145
+ * send go in order, so a record never outlives its work. */
1146
+ function sendWork({ watches, owner, log }, { sessionId, id, agentId }) {
1147
+ let owning = true;
1148
+ let recorded = false;
1149
+ let recording = Promise.resolve();
1150
+ const record = (write) => {
1151
+ recording = recording
1152
+ .then(write)
1153
+ .catch((error) => log(`recording the work of ${sessionId} failed: ${error?.message || error}`));
1154
+ return recording;
1155
+ };
1156
+ return {
1157
+ /** The work of every send `ids` this turn answered ends together: one
1158
+ * write drops all their records (a send standing down for the turn's
1159
+ * first leaves its record to that one write). */
1160
+ disown(ids = [id]) {
1161
+ owning = false;
1162
+ for (const done of ids) endBotWork(done);
1163
+ if (recorded || ids.length > 1) void record(() => watches.disown?.(ids));
1164
+ },
1165
+ /** Standing down for the turn's first send, which owns the same work. */
1166
+ standDown() {
1167
+ owning = false;
1168
+ endBotWork(id);
1169
+ },
1170
+ /** The send was accepted: unless it already settled (nothing then is left
1171
+ * to end the work), the work is the agent's, recorded when `notify` is off.
1172
+ * Returns the recording to await, or nothing when there is none. */
1173
+ claim(notify) {
1174
+ if (!owning) return undefined;
1175
+ noteBotWork({ runId: id, sessionId, agentId });
1176
+ if (notify) return undefined;
1177
+ recorded = true;
1178
+ return record(() => watches.own?.({ runId: id, agentId, taskSessionId: sessionId, owner, at: Date.now() }));
1179
+ },
1180
+ };
1181
+ }
1182
+
1183
+ /** Submit `prompt` as the origin agent's turn; its end (and a wait for the
1184
+ * user in it) reports to the agent's home. Resolves on intake. `submit`
1185
+ * takes the turn in another way than a prompt to `sessionId` (a message
1186
+ * into a group room, whose member answers in `sessionId`): given `{ id,
1187
+ * onSettled }`, it resolves `{ accepted }`. */
1188
+ async function runAgentTurn(
1189
+ deps,
1190
+ sessionId,
1191
+ prompt,
1192
+ origin,
1193
+ id = `atask_${randomUUID()}`,
1194
+ beforeReport = null,
1195
+ submit = null
1196
+ ) {
1197
+ const { watches, followToFinal, settlement, processToken, log } = deps;
1198
+ const sessions = deps.service();
1199
+ const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
1200
+ const dropWatch = () =>
1201
+ watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
1202
+ // `sendIds`: the sends this turn's end closes: this one, plus any that its
1203
+ // turn answered too (createSendSettlement).
1204
+ const turn = { runtime: null, sendIds: [id] };
1205
+ const { deliverReport, report } = sendReporter(deps, { sessionId, id, origin, beforeReport, turn });
1206
+ let unwatch = () => {};
1207
+ const work = sendWork({ watches, owner: processToken, log }, { sessionId, id, agentId: origin.agentId });
1208
+ // Set once the turn ended completed: the work and any report wait for the
1209
+ // final answer.
1210
+ let follow = null;
1211
+ let result;
1212
+ /** This send ends the turn `detail` for the sends `answered` (createSendSettlement). */
1213
+ const settleTurn = (detail, answered) => {
1214
+ turn.sendIds = answered;
1215
+ const status = turnStatus(detail);
1216
+ const text = detail?.result?.content || detail?.error || '';
1217
+ // A failed or cancelled turn ends at once; a completed one ends with
1218
+ // the task's final answer (followToFinal).
1219
+ if (status !== 'completed') {
1220
+ unwatch();
1221
+ work.disown(turn.sendIds);
1222
+ if (notify) void report(status, text);
1223
+ return;
1224
+ }
1225
+ follow = followToFinal({
1226
+ sessionId,
1227
+ runId: id,
1228
+ runtime: turn.runtime,
1229
+ immediate: { status, text },
1230
+ finish: async (endStatus, endText, answerId) => {
1231
+ unwatch();
1232
+ work.disown(turn.sendIds);
1233
+ if (notify) await report(endStatus, endText, { answerId });
1234
+ },
1235
+ // Each other answer of its chain: a progress update that wakes nobody
1236
+ // (only a send that reports gives them).
1237
+ ...(notify ? { interim: (answer) => deliverReport('working', answer.text, { answerId: answer.id }) } : {}),
1238
+ });
1239
+ follow.check();
1240
+ };
1241
+ try {
1242
+ const runtime = await sessions.materializeSession(sessionId);
1243
+ turn.runtime = runtime;
1244
+ const onState = notify ? approvalWaitReporter(id, report) : null;
1245
+ unwatch =
1246
+ runtime?.subscribe?.(() => {
1247
+ follow?.check();
1248
+ onState?.(runtime.getState?.() || {});
1249
+ }) || unwatch;
1250
+ // A bot's send to a session that is not its own task session reaches the
1251
+ // model inside the sender envelope (`envelopeName`, set by reportOrigin).
1252
+ const sent = origin.envelopeName
1253
+ ? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
1254
+ : prompt;
1255
+ const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
1256
+ const seq = settlement.nextSeq();
1257
+ result = await take({
1258
+ id,
1259
+ mode: 'prompt',
1260
+ transcriptMeta: sendTranscriptMeta(origin),
1261
+ // A room answers this send in a turn under another submission id (one
1262
+ // turn for several sends, or a fresh turn for one cut short): the
1263
+ // watch follows that turn, recorded before it runs.
1264
+ ...(notify ? { follow: (submissionId) => watches.follow?.(id, submissionId) } : {}),
1265
+ onSettled: (detail) =>
1266
+ settlement.join(detail, origin, {
1267
+ seq,
1268
+ id,
1269
+ notify,
1270
+ unwatch: () => {
1271
+ unwatch();
1272
+ work.standDown();
1273
+ },
1274
+ settle: (answered) => settleTurn(detail, answered),
1275
+ }),
1276
+ });
1277
+ } catch (error) {
1278
+ unwatch();
1279
+ work.disown();
1280
+ if (notify) await dropWatch();
1281
+ throw error;
1282
+ }
1283
+ const accepted = result?.accepted === true;
1284
+ if (!accepted) {
1285
+ unwatch();
1286
+ work.disown();
1287
+ if (notify) await dropWatch();
1288
+ } else {
1289
+ const recording = work.claim(notify);
1290
+ if (recording) await recording;
1291
+ }
1292
+ return sendResult(accepted, notify, id, noReport);
1293
+ }
1294
+
1295
+ /** Daemon start: re-attach every reporting turn an earlier process watched,
1296
+ * and every turn of a bot's that reported nothing but whose work it owned
1297
+ * (nothing is reported for those, they are only the bot's work again).
1298
+ * Resolves once each is reported or watched. */
1299
+ async function recoverTaskWatches({ watches, settleAgentResults, rearm, processToken, log }) {
1300
+ const earlier = (list) => list.filter((entry) => entry?.owner !== processToken);
1301
+ const pending = earlier(await watches.list());
1302
+ // A store without records of quiet work (`owned` is optional) has none; a
1303
+ // failed read of them never keeps the reporting turns from re-attaching.
1304
+ const quiet = earlier(
1305
+ await Promise.resolve(watches.owned?.() ?? []).catch((error) => {
1306
+ log(`reading the records of bot work failed: ${error?.message || error}`);
1307
+ return [];
1308
+ })
1309
+ );
1310
+ // The agents the restart killed become queued failure results first, so
1311
+ // each re-attached task session has them to take (followToFinal wakeOwed).
1312
+ if (pending.length) {
1313
+ await Promise.resolve()
1314
+ .then(settleAgentResults)
1315
+ .catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
1316
+ }
1317
+ const reattach = (work, options) =>
1318
+ rearm(work, options).catch((error) =>
1319
+ log(`re-attaching ${work?.taskSessionId} failed: ${error?.message || error}`)
1320
+ );
1321
+ await Promise.all([
1322
+ ...pending.map((watch) => reattach(watch)),
1323
+ ...quiet.map((work) => reattach(work, { quiet: true })),
1324
+ ]);
1325
+ return pending.length + quiet.length;
1326
+ }
1327
+
1328
+ /** The route a task session starts on: it runs in coding mode under Lead: an
1329
+ * explicit model wins, else the Lead route, else the last-used model. Never a
1330
+ * model-less session — the composer would show no model and its first turn
1331
+ * would fail. The catalog is read only when a display name or an effort
1332
+ * needs it. */
1333
+ async function taskRoute({ modelRoute, modelCatalog, leadRoute, lastUsedRoute, service }, args, callerSessionId) {
1334
+ let catalog = null;
1335
+ const catalogRows = async () => (catalog ??= await modelCatalog(callerSessionId));
1336
+ let route = clean(args.model)
1337
+ ? await modelRoute(clean(args.model), async (text) =>
1338
+ routeByDisplayName(text, await catalogRows(), args.effort !== undefined)
1339
+ )
1340
+ : (await leadRoute()) || (await lastUsedRoute(service()));
1341
+ if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
1342
+ // An explicit effort is the setting the menu's effort pick makes (session.effort,
1343
+ // Auto effort off); it wins over one a model ref or name carried.
1344
+ if (args.effort !== undefined) route = withEffort(route, args.effort, await catalogRows());
1345
+ return route;
1346
+ }
1347
+
1348
+ /** Nothing was sent after the worktree failure `error`: drop the empty task
1349
+ * session `sessionId` so a retry starts clean, then throw `error` (an
1350
+ * orphanSessionError when the session could not be dropped). */
1351
+ async function dropEmptyTaskSession(sessions, sessionId, error, log) {
1352
+ let dropFailure = null;
1353
+ try {
1354
+ const dropped = await sessions.configureSession(
1355
+ { sessionId, action: 'deleteSession', args: [sessionId] },
1356
+ { revision: 1 }
1357
+ );
1358
+ // A resolved `false` is a refusal, not a deletion.
1359
+ if (dropped?.value === false) dropFailure = new Error('the session service refused the deletion');
1360
+ } catch (deleteError) {
1361
+ dropFailure = deleteError;
1362
+ }
1363
+ if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
1364
+ if (!dropFailure) throw error;
1365
+ throw orphanSessionError(error, sessionId, dropFailure);
1366
+ }
1367
+
1368
+ /** Create the task session for `brief` and start it; returns at once.
1369
+ * `callerScope` is the caller's trusted worktree scope (worktree-access.mjs
1370
+ * worktreeCallerScope): provisioning a worktree is a mutation only an
1371
+ * attended conversation may make, so an explicit worktree from any other
1372
+ * caller is refused and the New task default keeps it in the Project folder. */
1373
+ async function createTaskSession(
1374
+ deps,
1375
+ args = {},
1376
+ {
1377
+ callerSessionId,
1378
+ callerAgentId,
1379
+ callerCwd,
1380
+ callerHop = 0,
1381
+ callerBotChain = [],
1382
+ callerScope,
1383
+ attachmentParts = [],
1384
+ } = {}
1385
+ ) {
1386
+ const { service, listProjects, newTaskDefault, deliver, agentTurn, log } = deps;
1387
+ const { brief, location } = createRequest(args, callerAgentId, callerScope);
1388
+ const where = createWhere(args, await listProjects(), callerCwd, location);
1389
+ const requestId = `atask_${randomUUID()}`;
1390
+ const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
1391
+ const taskOrigin = {
1392
+ agentId: callerAgentId,
1393
+ sessionId: callerSessionId,
1394
+ requestId,
1395
+ title,
1396
+ notify: args.notify !== false,
1397
+ };
1398
+ const route = await taskRoute(deps, args, callerSessionId);
1399
+ const sessions = service();
1400
+ // Where it starts: the `worktree` option, else the user's New task default
1401
+ // for a caller that may provision one. Decided before the session exists,
1402
+ // so a failure here leaves nothing behind.
1403
+ const explicit = location.mode === 'worktree';
1404
+ const inWorktree =
1405
+ explicit ||
1406
+ (location.mode === 'default' &&
1407
+ where.project &&
1408
+ callerScope?.attended === true &&
1409
+ (await newTaskDefault()) === 'worktree');
1410
+ const created = await sessions.createSession({
1411
+ ...route,
1412
+ cwd: where.cwd,
1413
+ desktopSession: where.desktopSession,
1414
+ sessionProfile: { taskOrigin },
1415
+ });
1416
+ const sessionId = String(created?.sessionId || '');
1417
+ if (!sessionId) throw new Error('session: task session creation returned no session id');
1418
+ if (inWorktree) {
1419
+ try {
1420
+ await requestTaskWorktree(sessions, sessionId, {
1421
+ title,
1422
+ requestId,
1423
+ name: location.name,
1424
+ confirmLarge: args.confirm_large === true,
1425
+ explicit,
1426
+ });
1427
+ } catch (error) {
1428
+ await dropEmptyTaskSession(sessions, sessionId, error, log);
1429
+ }
1430
+ }
1431
+ const { ready, started } = startedGate();
1432
+ // The brief carries the files the agent forwarded (workspace-tools.mjs).
1433
+ const prompt = handoffContent(brief, attachmentParts);
1434
+ const botChain = botChainOf(callerBotChain);
1435
+ const { accepted, reportsBack, reportsBackReason } = await agentTurn(
1436
+ sessionId,
1437
+ prompt,
1438
+ { ...taskOrigin, ...(callerHop > 0 ? { hop: callerHop } : {}), ...(botChain.length ? { botChain } : {}) },
1439
+ requestId,
1440
+ ready
1441
+ );
1442
+ if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
1443
+ if (reportsBack) {
1444
+ await deliver(callerAgentId, {
1445
+ runId: `${requestId}_started`,
1446
+ taskSessionId: sessionId,
1447
+ title,
1448
+ status: 'started',
1449
+ ...roomFrom(callerSessionId),
1450
+ }).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
1451
+ }
1452
+ started();
1453
+ // Started, not done: the final answer is reported later.
1454
+ return {
1455
+ session_id: sessionId,
1456
+ status: 'started',
1457
+ title,
1458
+ project: where.project?.path ?? null,
1459
+ ...(taskOrigin.notify && !reportsBack ? { reportsBack: false, reportsBackReason } : {}),
1460
+ };
1461
+ }
1462
+
1463
+ const homeDelivery = () => import('./agent-home-delivery.mjs');
1464
+
1465
+ // The durable watch of each reporting turn until its end is queued. A bot's
1466
+ // turn that reports nothing (reports off, or a watch that could not be
1467
+ // stored) keeps a record of its work instead (`own`, `owned`, `disown`),
1468
+ // which no report and no hand-off follows: a restart re-attaches both.
1469
+ const HOME_DELIVERY_WATCHES = {
1470
+ add: async (watch) => (await homeDelivery()).watchAgentTask(watch),
1471
+ list: async () => (await homeDelivery()).agentTaskWatches(),
1472
+ remove: async (runId) => (await homeDelivery()).unwatchAgentTask(runId),
1473
+ follow: async (runId, submissionId) => (await homeDelivery()).followAgentTask(runId, submissionId),
1474
+ own: async (work) => (await homeDelivery()).ownAgentTask(work),
1475
+ owned: async () => (await homeDelivery()).agentOwnedTasks(),
1476
+ disown: async (runId) => (await homeDelivery()).disownAgentTask(runId),
1477
+ };
1478
+
1097
1479
  export function createAgentTaskSessions({
1098
1480
  getSessionService,
1099
- deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
1100
- // The durable watch of each reporting turn until its end is queued. A bot's
1101
- // turn that reports nothing (reports off, or a watch that could not be
1102
- // stored) keeps a record of its work instead (`own`, `owned`, `disown`),
1103
- // which no report and no hand-off follows: a restart re-attaches both.
1104
- watches = {
1105
- add: async (watch) => (await import('./agent-home-delivery.mjs')).watchAgentTask(watch),
1106
- list: async () => (await import('./agent-home-delivery.mjs')).agentTaskWatches(),
1107
- remove: async (runId) => (await import('./agent-home-delivery.mjs')).unwatchAgentTask(runId),
1108
- follow: async (runId, submissionId) =>
1109
- (await import('./agent-home-delivery.mjs')).followAgentTask(runId, submissionId),
1110
- own: async (work) => (await import('./agent-home-delivery.mjs')).ownAgentTask(work),
1111
- owned: async () => (await import('./agent-home-delivery.mjs')).agentOwnedTasks(),
1112
- disown: async (runId) => (await import('./agent-home-delivery.mjs')).disownAgentTask(runId),
1113
- },
1481
+ deliver = async (agentId, report) => (await homeDelivery()).deliverAgentTask(agentId, report),
1482
+ watches = HOME_DELIVERY_WATCHES,
1114
1483
  // Submissions still queued (durably) for a session after a restart.
1115
1484
  queuedSubmissionIds = async (sessionId) =>
1116
1485
  (await (await import('../tui/session/tui-steering-persist.mjs')).readTuiSteeringPersist(sessionId))
@@ -1177,189 +1546,18 @@ export function createAgentTaskSessions({
1177
1546
  const followToFinal = (follow) =>
1178
1547
  followTaskToFinal({ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, log }, follow);
1179
1548
 
1180
- const settlement = createSendSettlement(log);
1181
-
1182
- /** Submit `prompt` as the origin agent's turn; its end (and a wait for the
1183
- * user in it) reports to the agent's home. Resolves on intake. `submit`
1184
- * takes the turn in another way than a prompt to `sessionId` (a message
1185
- * into a group room, whose member answers in `sessionId`): given `{ id,
1186
- * onSettled }`, it resolves `{ accepted }`. */
1187
- async function agentTurn(
1188
- sessionId,
1189
- prompt,
1190
- origin,
1191
- id = `atask_${randomUUID()}`,
1192
- beforeReport = null,
1193
- submit = null
1194
- ) {
1195
- const sessions = service();
1196
- const target = origin.target === 'session' ? { target: 'session' } : {};
1197
- const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
1198
- const dropWatch = () =>
1199
- watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
1200
- let runtime = null;
1201
- // The sends this turn's end closes: this one, plus any that its turn
1202
- // answered too (createSendSettlement).
1203
- let sendIds = [id];
1204
- // A report that cannot be queued is retried in this process; when every try
1205
- // fails the durable watch stays and restart recovery delivers it (the queue
1206
- // dedupes by runId, so a retry never posts twice).
1207
- // `answerId`: the turn whose answer a progress update or the end carries.
1208
- // `deliverReport` rejects once every try failed (a progress update is
1209
- // tried again at the follow's next check); `report` logs it instead.
1210
- const deliverReport = (status, text = '', { approvalId = '', answerId = '' } = {}) =>
1211
- Promise.resolve(beforeReport).then(async () =>
1212
- deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
1213
- runId: turnReportRunId(sessionId, id, status, { approvalId, answerId }),
1214
- taskSessionId: sessionId,
1215
- title: origin.title,
1216
- status,
1217
- text,
1218
- ...target,
1219
- ...(origin.hop > 0 ? { hop: origin.hop } : {}),
1220
- ...roomFrom(origin.sessionId),
1221
- // A progress update moves this send's durable mark to its turn
1222
- // (agent-home-delivery.mjs enqueueTask), in the same write.
1223
- ...(status === 'working' ? { progressWatchId: id, answerId } : {}),
1224
- // A turn's end brings back the files it produced, when the agent
1225
- // may receive them (reportOrigin `textOnly`).
1226
- ...(status === 'blocked' || status === 'working'
1227
- ? {}
1228
- : {
1229
- watchId: id,
1230
- ...(sendIds.length > 1 ? { watchIds: sendIds } : {}),
1231
- ...progressOf(id, answerId),
1232
- ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)),
1233
- }),
1234
- })
1235
- );
1236
- const report = (status, text = '', options = {}) =>
1237
- deliverReport(status, text, options).catch((error) =>
1238
- log(`report of ${sessionId} failed: ${error?.message || error}`)
1239
- );
1240
- let unwatch = () => {};
1241
- // The agent's work in `sessionId`: owned from the send's acceptance until
1242
- // it settles or is cancelled, whether or not it reports (`notify`). A send
1243
- // that holds no report watch keeps a durable record of the work instead
1244
- // (`watches.own`), so a restart re-attaches it (recover); the writes of
1245
- // one send go in order, so a record never outlives its work.
1246
- let owning = true;
1247
- let recorded = false;
1248
- let recording = Promise.resolve();
1249
- const record = (write) => {
1250
- recording = recording
1251
- .then(write)
1252
- .catch((error) => log(`recording the work of ${sessionId} failed: ${error?.message || error}`));
1253
- return recording;
1254
- };
1255
- // The work of every send `ids` this turn answered ends together: one
1256
- // write drops all their records (a send standing down for the turn's
1257
- // first leaves its record to that one write).
1258
- const disown = (ids = [id]) => {
1259
- owning = false;
1260
- for (const done of ids) endBotWork(done);
1261
- if (recorded || ids.length > 1) void record(() => watches.disown?.(ids));
1262
- };
1263
- const standDown = () => {
1264
- unwatch();
1265
- owning = false;
1266
- endBotWork(id);
1267
- };
1268
- // Set once the turn ended completed: the work and any report wait for the
1269
- // final answer.
1270
- let follow = null;
1271
- let result;
1272
- /** This send ends the turn `detail` for the sends `answered` (createSendSettlement). */
1273
- const settleTurn = (detail, answered) => {
1274
- sendIds = answered;
1275
- const status = turnStatus(detail);
1276
- const text = detail?.result?.content || detail?.error || '';
1277
- // A failed or cancelled turn ends at once; a completed one ends with
1278
- // the task's final answer (followToFinal).
1279
- if (status !== 'completed') {
1280
- unwatch();
1281
- disown(sendIds);
1282
- if (notify) void report(status, text);
1283
- return;
1284
- }
1285
- follow = followToFinal({
1286
- sessionId,
1287
- runId: id,
1288
- runtime,
1289
- immediate: { status, text },
1290
- finish: async (endStatus, endText, answerId) => {
1291
- unwatch();
1292
- disown(sendIds);
1293
- if (notify) await report(endStatus, endText, { answerId });
1294
- },
1295
- // Each other answer of its chain: a progress update that wakes nobody
1296
- // (only a send that reports gives them).
1297
- ...(notify ? { interim: (answer) => deliverReport('working', answer.text, { answerId: answer.id }) } : {}),
1298
- });
1299
- follow.check();
1300
- };
1301
- try {
1302
- runtime = await sessions.materializeSession(sessionId);
1303
- const onState = notify ? approvalWaitReporter(id, report) : null;
1304
- unwatch =
1305
- runtime?.subscribe?.(() => {
1306
- follow?.check();
1307
- onState?.(runtime.getState?.() || {});
1308
- }) || unwatch;
1309
- // A bot's send to a session that is not its own task session reaches the
1310
- // model inside the sender envelope (`envelopeName`, set by reportOrigin).
1311
- const sent = origin.envelopeName
1312
- ? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
1313
- : prompt;
1314
- const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
1315
- const seq = settlement.nextSeq();
1316
- result = await take({
1317
- id,
1318
- mode: 'prompt',
1319
- transcriptMeta: sendTranscriptMeta(origin),
1320
- // A room answers this send in a turn under another submission id (one
1321
- // turn for several sends, or a fresh turn for one cut short): the
1322
- // watch follows that turn, recorded before it runs.
1323
- ...(notify ? { follow: (submissionId) => watches.follow?.(id, submissionId) } : {}),
1324
- onSettled: (detail) =>
1325
- settlement.join(detail, origin, {
1326
- seq,
1327
- id,
1328
- notify,
1329
- // Standing down for the turn's first send, which owns the same work.
1330
- unwatch: standDown,
1331
- settle: (answered) => settleTurn(detail, answered),
1332
- }),
1333
- });
1334
- } catch (error) {
1335
- unwatch();
1336
- disown();
1337
- if (notify) await dropWatch();
1338
- throw error;
1339
- }
1340
- const accepted = result?.accepted === true;
1341
- if (!accepted) {
1342
- unwatch();
1343
- disown();
1344
- if (notify) await dropWatch();
1345
- } else if (owning) {
1346
- // Unless it already settled: nothing then is left to end the work.
1347
- noteBotWork({ runId: id, sessionId, agentId: origin.agentId });
1348
- if (!notify) {
1349
- recorded = true;
1350
- await record(() =>
1351
- watches.own?.({
1352
- runId: id,
1353
- agentId: origin.agentId,
1354
- taskSessionId: sessionId,
1355
- owner: processToken,
1356
- at: Date.now(),
1357
- })
1358
- );
1359
- }
1360
- }
1361
- return sendResult(accepted, notify, id, noReport);
1362
- }
1549
+ const turnDeps = {
1550
+ service,
1551
+ watches,
1552
+ deliver,
1553
+ retryDelaysMs,
1554
+ artifactFields,
1555
+ followToFinal,
1556
+ settlement: createSendSettlement(log),
1557
+ processToken,
1558
+ log,
1559
+ };
1560
+ const agentTurn = (...turnArgs) => runAgentTurn(turnDeps, ...turnArgs);
1363
1561
 
1364
1562
  const rearm = (watch, options) =>
1365
1563
  rearmTaskWatch(
@@ -1377,167 +1575,26 @@ export function createAgentTaskSessions({
1377
1575
  options
1378
1576
  );
1379
1577
 
1380
- /** Daemon start: re-attach every reporting turn an earlier process watched,
1381
- * and every turn of a bot's that reported nothing but whose work it owned
1382
- * (nothing is reported for those, they are only the bot's work again).
1383
- * Resolves once each is reported or watched. */
1384
- async function recover() {
1385
- const earlier = (list) => list.filter((entry) => entry?.owner !== processToken);
1386
- const pending = earlier(await watches.list());
1387
- const quiet = earlier(
1388
- await Promise.resolve(watches.owned?.() ?? [])
1389
- .then((list) => (Array.isArray(list) ? list : []))
1390
- .catch((error) => {
1391
- log(`reading the records of bot work failed: ${error?.message || error}`);
1392
- return [];
1393
- })
1394
- );
1395
- // The agents the restart killed become queued failure results first, so
1396
- // each re-attached task session has them to take (followToFinal wakeOwed).
1397
- if (pending.length) {
1398
- await Promise.resolve()
1399
- .then(settleAgentResults)
1400
- .catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
1401
- }
1402
- const reattach = (work, options) =>
1403
- rearm(work, options).catch((error) =>
1404
- log(`re-attaching ${work?.taskSessionId} failed: ${error?.message || error}`)
1405
- );
1406
- await Promise.all([
1407
- ...pending.map((watch) => reattach(watch)),
1408
- ...quiet.map((work) => reattach(work, { quiet: true })),
1409
- ]);
1410
- return pending.length + quiet.length;
1411
- }
1412
-
1413
- /** Create the task session for `brief` and start it; returns at once.
1414
- * `callerScope` is the caller's trusted worktree scope (worktree-access.mjs
1415
- * worktreeCallerScope): provisioning a worktree is a mutation only an
1416
- * attended conversation may make, so an explicit worktree from any other
1417
- * caller is refused and the New task default keeps it in the Project folder. */
1418
- async function create(
1419
- args = {},
1420
- {
1421
- callerSessionId,
1422
- callerAgentId,
1423
- callerCwd,
1424
- callerHop = 0,
1425
- callerBotChain = [],
1426
- callerScope,
1427
- attachmentParts = [],
1428
- } = {}
1429
- ) {
1430
- const { brief, location } = createRequest(args, callerAgentId, callerScope);
1431
- const where = createWhere(args, await listProjects(), callerCwd, location);
1432
- const requestId = `atask_${randomUUID()}`;
1433
- const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
1434
- const taskOrigin = {
1435
- agentId: callerAgentId,
1436
- sessionId: callerSessionId,
1437
- requestId,
1438
- title,
1439
- notify: args.notify !== false,
1440
- };
1441
- // A task session runs in coding mode under Lead: an explicit model wins,
1442
- // else the Lead route, else the last-used model. Never a model-less
1443
- // session — the composer would show no model and its first turn would fail.
1444
- // The catalog is read only when a display name or an effort needs it.
1445
- let catalog = null;
1446
- const catalogRows = async () => (catalog ??= await modelCatalog(callerSessionId));
1447
- let route = clean(args.model)
1448
- ? await modelRoute(clean(args.model), async (text) =>
1449
- routeByDisplayName(text, await catalogRows(), args.effort !== undefined)
1450
- )
1451
- : (await leadRoute()) || (await lastUsedRoute(service()));
1452
- if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
1453
- // An explicit effort is the setting the menu's effort pick makes (session.effort,
1454
- // Auto effort off); it wins over one a model ref or name carried.
1455
- if (args.effort !== undefined) route = withEffort(route, args.effort, await catalogRows());
1456
- const sessions = service();
1457
- // Where it starts: the `worktree` option, else the user's New task default
1458
- // for a caller that may provision one. Decided before the session exists,
1459
- // so a failure here leaves nothing behind.
1460
- const explicit = location.mode === 'worktree';
1461
- const inWorktree =
1462
- explicit ||
1463
- (location.mode === 'default' &&
1464
- where.project &&
1465
- callerScope?.attended === true &&
1466
- (await newTaskDefault()) === 'worktree');
1467
- const created = await sessions.createSession({
1468
- ...route,
1469
- cwd: where.cwd,
1470
- desktopSession: where.desktopSession,
1471
- sessionProfile: { taskOrigin },
1472
- });
1473
- const sessionId = String(created?.sessionId || '');
1474
- if (!sessionId) throw new Error('session: task session creation returned no session id');
1475
- if (inWorktree) {
1476
- try {
1477
- await requestTaskWorktree(sessions, sessionId, {
1478
- title,
1479
- requestId,
1480
- name: location.name,
1481
- confirmLarge: args.confirm_large === true,
1482
- explicit,
1483
- });
1484
- } catch (error) {
1485
- // Nothing was sent: drop the empty session so a retry starts clean.
1486
- let dropFailure = null;
1487
- try {
1488
- const dropped = await sessions.configureSession(
1489
- { sessionId, action: 'deleteSession', args: [sessionId] },
1490
- { revision: 1 }
1491
- );
1492
- // A resolved `false` is a refusal, not a deletion.
1493
- if (dropped?.value === false) dropFailure = new Error('the session service refused the deletion');
1494
- } catch (deleteError) {
1495
- dropFailure = deleteError;
1496
- }
1497
- if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
1498
- if (!dropFailure) throw error;
1499
- throw orphanSessionError(error, sessionId, dropFailure);
1500
- }
1501
- }
1502
- const { ready, started } = startedGate();
1503
- // The brief carries the files the agent forwarded (workspace-tools.mjs).
1504
- const prompt = handoffContent(brief, attachmentParts);
1505
- const botChain = botChainOf(callerBotChain);
1506
- const { accepted, reportsBack, reportsBackReason } = await agentTurn(
1507
- sessionId,
1508
- prompt,
1509
- { ...taskOrigin, ...(callerHop > 0 ? { hop: callerHop } : {}), ...(botChain.length ? { botChain } : {}) },
1510
- requestId,
1511
- ready
1512
- );
1513
- if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
1514
- if (reportsBack) {
1515
- await deliver(callerAgentId, {
1516
- runId: `${requestId}_started`,
1517
- taskSessionId: sessionId,
1518
- title,
1519
- status: 'started',
1520
- ...roomFrom(callerSessionId),
1521
- }).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
1522
- }
1523
- started();
1524
- // Started, not done: the final answer is reported later.
1525
- return {
1526
- session_id: sessionId,
1527
- status: 'started',
1528
- title,
1529
- project: where.project?.path ?? null,
1530
- ...(taskOrigin.notify && !reportsBack ? { reportsBack: false, reportsBackReason } : {}),
1531
- };
1532
- }
1578
+ const createDeps = {
1579
+ service,
1580
+ listProjects,
1581
+ newTaskDefault,
1582
+ modelRoute,
1583
+ modelCatalog,
1584
+ leadRoute,
1585
+ lastUsedRoute,
1586
+ deliver,
1587
+ agentTurn,
1588
+ log,
1589
+ };
1533
1590
 
1534
1591
  const reportOrigin = (sessionId, agentId, callerSessionId) =>
1535
1592
  sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId);
1536
1593
 
1537
1594
  return {
1538
- create,
1595
+ create: (args, caller) => createTaskSession(createDeps, args, caller),
1539
1596
  reportOrigin,
1540
- recover,
1597
+ recover: () => recoverTaskWatches({ watches, settleAgentResults, rearm, processToken, log }),
1541
1598
  send: (sessionId, prompt, origin, submit = null) => agentTurn(sessionId, prompt, origin, undefined, null, submit),
1542
1599
  };
1543
1600
  }