mixdog 1.3.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +2 -11
  4. package/scripts/daemon-stub.mjs +1 -1
  5. package/scripts/gate-local.mjs +4 -2
  6. package/scripts/native-release-status.mjs +2 -2
  7. package/scripts/recall-history-cases.json +1 -1
  8. package/scripts/suite-health.mjs +10 -1
  9. package/scripts/test.mjs +7 -11
  10. package/src/agents/reviewer/AGENT.md +7 -11
  11. package/src/agents/reviewer/agent.json +1 -1
  12. package/src/defaults/shipped-history.json +17 -1
  13. package/src/defaults/skills/browser-use/SKILL.md +59 -11
  14. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  15. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  16. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  17. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  18. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  19. package/src/defaults/skills/setup/SKILL.md +22 -0
  20. package/src/defaults/skills/setup/references/actions.md +39 -5
  21. package/src/defaults/skills/setup/references/surfaces.md +11 -2
  22. package/src/defaults/skills/visualize/SKILL.md +2 -1
  23. package/src/lib/rules-builder.cjs +10 -8
  24. package/src/rules/agent/AGENT.md +6 -6
  25. package/src/rules/lead/LEAD.md +18 -20
  26. package/src/rules/shared/05-tool-calls.md +5 -6
  27. package/src/rules/shared/10-tool-workflow.md +2 -0
  28. package/src/rules/shared/60-verification.md +7 -5
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  30. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  31. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  32. package/src/runtime/agent/orchestrator/config.mjs +17 -1
  33. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  35. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  36. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
  39. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
  40. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
  41. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  42. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  45. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
  46. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  47. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  48. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  49. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
  50. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  52. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
  53. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  54. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  55. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  56. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  57. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  58. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  64. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  65. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  68. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  69. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  71. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  72. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  73. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  76. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  77. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
  78. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  79. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
  82. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
  83. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  84. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  86. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
  88. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  90. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
  91. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  92. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
  93. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  94. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  97. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
  98. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  99. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  101. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  102. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  103. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  104. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  105. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  106. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  107. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  109. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  110. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  112. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  113. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  114. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  115. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  116. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  117. package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
  118. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  120. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  121. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  122. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  123. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  124. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  125. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  126. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  127. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
  128. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  129. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  130. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  131. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  132. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  133. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  134. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  135. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
  136. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  137. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  138. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  141. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  142. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  143. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  144. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  145. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  146. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  147. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  148. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  149. package/src/runtime/browser-bridge/client.mjs +73 -8
  150. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  151. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  152. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  153. package/src/runtime/channels/lib/config.mjs +6 -61
  154. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  155. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  156. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  157. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  158. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  159. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  160. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  161. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  162. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  163. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  164. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  165. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  166. package/src/runtime/channels/lib/scheduler.mjs +33 -147
  167. package/src/runtime/channels/lib/schedules-db.mjs +34 -11
  168. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  169. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
  170. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  171. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  172. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  173. package/src/runtime/channels/lib/worker-main.mjs +10 -34
  174. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  175. package/src/runtime/computer-bridge/actions.mjs +9 -1
  176. package/src/runtime/computer-bridge/client.mjs +180 -8
  177. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  178. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  179. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  180. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  181. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  182. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  183. package/src/runtime/connectors/google-mcp.mjs +10 -12
  184. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  185. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  186. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  187. package/src/runtime/session-search/session-search-db.mjs +73 -9
  188. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  189. package/src/runtime/shared/abort-race.mjs +89 -0
  190. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  191. package/src/runtime/shared/agent-kind.mjs +4 -3
  192. package/src/runtime/shared/bounded-download.mjs +8 -3
  193. package/src/runtime/shared/config.mjs +2 -13
  194. package/src/runtime/shared/connector-accounts.mjs +20 -10
  195. package/src/runtime/shared/connector-catalog.d.mts +29 -0
  196. package/src/runtime/shared/connector-catalog.mjs +150 -0
  197. package/src/runtime/shared/connector-triggers.mjs +22 -19
  198. package/src/runtime/shared/defaults-separation.mjs +2 -14
  199. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  200. package/src/runtime/shared/err-text.mjs +24 -15
  201. package/src/runtime/shared/file-permissions.d.mts +0 -8
  202. package/src/runtime/shared/file-permissions.mjs +0 -11
  203. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  204. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  205. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  206. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  207. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  208. package/src/runtime/shared/mixy-presence.mjs +22 -0
  209. package/src/runtime/shared/orchestration.d.mts +2 -0
  210. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  211. package/src/runtime/shared/profile-config.cjs +1 -1
  212. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  213. package/src/runtime/shared/prompt-origin.mjs +21 -0
  214. package/src/runtime/shared/provider-accounts.mjs +7 -1
  215. package/src/runtime/shared/provider-display-name.mjs +29 -0
  216. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  217. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  218. package/src/runtime/shared/routine-trigger.mjs +10 -0
  219. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  220. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  221. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  222. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  223. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  224. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  225. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  226. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  227. package/src/runtime/terminal-bridge/client.mjs +10 -1
  228. package/src/runtime/visualize-bridge/client.mjs +27 -8
  229. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  230. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  231. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  232. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  233. package/src/session-runtime/agent-home-delivery.mjs +448 -185
  234. package/src/session-runtime/agent-task-sessions.mjs +329 -180
  235. package/src/session-runtime/boot/apis.mjs +9 -0
  236. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  237. package/src/session-runtime/boot/providers.mjs +3 -0
  238. package/src/session-runtime/boot/tools.mjs +6 -1
  239. package/src/session-runtime/config-lifecycle.mjs +6 -0
  240. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  241. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  242. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  243. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  244. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  245. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  246. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  247. package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
  248. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  249. package/src/session-runtime/mixy-heartbeat.mjs +153 -33
  250. package/src/session-runtime/notification-bus.mjs +31 -117
  251. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  252. package/src/session-runtime/notification-listeners.mjs +76 -0
  253. package/src/session-runtime/owned-automation-run.mjs +2 -1
  254. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  255. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  256. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  257. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  258. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  259. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  260. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  261. package/src/session-runtime/routine-owner.mjs +40 -9
  262. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  263. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  264. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  265. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  266. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  267. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  268. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  269. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  270. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  271. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  272. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  273. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  274. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  275. package/src/session-runtime/services/channel-admin.mjs +7 -13
  276. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  277. package/src/session-runtime/services/seeds.mjs +0 -28
  278. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  279. package/src/session-runtime/session-create/session-options.mjs +1 -0
  280. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  281. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  282. package/src/session-runtime/settings-system-api.mjs +13 -1
  283. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  284. package/src/session-runtime/setup-tool/executor.mjs +145 -22
  285. package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
  286. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  287. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
  288. package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
  289. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  290. package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
  291. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  292. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  293. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  294. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  295. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  296. package/src/standalone/channel-transport.mjs +1 -1
  297. package/src/standalone/daemon-channels-loader.mjs +10 -8
  298. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  299. package/src/standalone/daemon-session-control/request.mjs +51 -0
  300. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  301. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  302. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  303. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  304. package/src/standalone/daemon-session-control.mjs +124 -188
  305. package/src/standalone/daemon.mjs +26 -3
  306. package/src/standalone/session-client.mjs +7 -3
  307. package/src/standalone/session-protocol.mjs +5 -0
  308. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  309. package/src/standalone/session-service/quota-resume.mjs +3 -0
  310. package/src/standalone/session-service/retention/busy.mjs +65 -1
  311. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  312. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  313. package/src/standalone/session-tool-output.mjs +15 -7
  314. package/src/tui/app/model-options.mjs +3 -28
  315. package/src/tui/dist/index.mjs +58 -64
  316. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  317. package/src/tui/session/initial-state.mjs +3 -0
  318. package/src/tui/session/notice-surface.mjs +7 -1
  319. package/src/tui/session/queue-helpers.mjs +3 -42
  320. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  321. package/src/tui/session/session-api/routes-write.mjs +5 -1
  322. package/src/tui/session/session-api/routes.mjs +1 -0
  323. package/src/tui/session/session-api/settings.mjs +17 -1
  324. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  325. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  326. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  327. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  328. package/src/tui/session/session-flow/drain.mjs +118 -149
  329. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  330. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  331. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  332. package/src/tui/session/tool-approval-request.mjs +39 -0
  333. package/src/tui/session/tool-approval-settle.mjs +62 -0
  334. package/src/tui/session/tool-approval.mjs +69 -115
  335. package/src/tui/session/tui-steering-persist.mjs +8 -1
  336. package/src/tui/session/turn-stream.mjs +19 -2
  337. package/src/tui/session/turn.mjs +1 -0
  338. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  339. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  340. package/scripts/code-graph-description-contract.mjs +0 -188
  341. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -1,56 +1,81 @@
1
- import { SESSION_ID_PATTERN } from '../runtime/agent/orchestrator/runtime-core/session-id.mjs';
2
- import { sessionListOutput, sessionReadOutput, SESSION_TOOL_OUTPUT_BYTES } from './session-tool-output.mjs';
3
- import { createAgentTaskSessions } from '../session-runtime/agent-task-sessions.mjs';
4
- import { assertWorktreeMutationAllowed } from '../session-runtime/services/worktree-access.mjs';
5
- import { createBotStatus, scanCatalog, scopedBot, sessionBotOf } from '../session-runtime/bot-status.mjs';
1
+ import {
2
+ sessionListOutput,
3
+ sessionReadOutput,
4
+ SESSION_TITLE_LIMIT,
5
+ SESSION_TOOL_OUTPUT_BYTES,
6
+ } from './session-tool-output.mjs';
7
+ import { createAgentTaskSessions, taskSessionPendingWork } from '../session-runtime/agent-task-sessions.mjs';
8
+ import { createBotStatus, scanCatalog, scopedBot } from '../session-runtime/bot-status.mjs';
6
9
  import { handoffContent } from '../session-runtime/handoff-attachments.mjs';
7
10
  import { checkEffort, readModelCatalog } from '../session-runtime/task-session-route.mjs';
8
- import {
9
- BOT_HOP_LIMIT_ERROR,
10
- MAX_BOT_HOPS,
11
- assertBotChainTarget,
12
- botChainOf,
13
- rememberSubmissionBotChain,
14
- } from '../runtime/shared/bot-hops.mjs';
11
+ import { assertBotChainTarget, botChainOf } from '../runtime/shared/bot-hops.mjs';
15
12
  import { agentIdOfHomeSession } from '../runtime/agent/orchestrator/session/agent-home.mjs';
16
13
  import { rebindDelegation } from '../runtime/agent/orchestrator/session/human-gate/delegation.mjs';
17
- import { isAgentOnlySession } from '../runtime/agent/orchestrator/session/store-summary-visibility.mjs';
18
14
  import { readSessionSidebarState } from '../runtime/shared/session-sidebar-host.mjs';
19
- import { agentRoomHost } from '../runtime/shared/agent-room-host.mjs';
15
+ import { roomIdOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
16
+ import { escapeHarnessTags } from '../runtime/shared/shell-output-summary.mjs';
17
+ import { truncateToolText } from '../runtime/shared/tool-primitives.mjs';
18
+ import { requestCaller, targetRequest } from './daemon-session-control/request.mjs';
19
+ import { createWorktreeControl } from './daemon-session-control/worktree-control.mjs';
20
+ import { createRouteControl } from './daemon-session-control/route-control.mjs';
20
21
  import {
21
- roomIdOfSession,
22
- roomMemberOfSession,
23
- roomSessionId,
24
- } from '../runtime/agent/orchestrator/session/agent-room.mjs';
25
-
26
- const TARGET_ACTIONS = new Set(['status', 'read', 'send', 'cancel', 'delete']);
27
- const MUTATING_ACTIONS = new Set(['send', 'cancel', 'delete']);
28
- // The workspace methods `worktree` reaches on another session (all on the
29
- // session protocol's read/configure allowlists).
30
- const WORKTREE_METHODS = new Set([
31
- 'getBranchMenu',
32
- 'requestWorktree',
33
- 'selectBranch',
34
- 'mergeWorkspace',
35
- 'removeWorktree',
36
- ]);
37
- // Why a send from a conversation that is no bot's reports nothing back.
38
- const NOT_BOT_REASON = 'only a bot conversation gets session results reported back; check that session for the result';
22
+ TOP_CURSOR,
23
+ assertListFlags,
24
+ listOutputOptions,
25
+ listRowFilter,
26
+ listedSessions,
27
+ liveCandidates,
28
+ liveRowOverlay,
29
+ pinnedLiveRows,
30
+ } from './daemon-session-control/list-page.mjs';
31
+ import { roomAddressee, roomSubmitter, stopRoomExchange } from './daemon-session-control/room-host.mjs';
32
+ import {
33
+ assertSendRequest,
34
+ chainedHop,
35
+ chainedOrigin,
36
+ effortApplier,
37
+ unreportedSendResult,
38
+ } from './daemon-session-control/send-turn.mjs';
39
39
 
40
- /** The member a send to room log `sessionId` addresses, through the desktop
41
- * room host: `{ host, agentId, sessionId, text }`; throws when no desktop is
42
- * attached or no member resolves. */
43
- async function roomAddressee(sessionId, args) {
44
- let host;
45
- try {
46
- host = agentRoomHost();
47
- } catch (error) {
48
- throw new Error(`session: cannot send to ${sessionId}: ${error.message}`);
40
+ /** Sessions with live work now, from the sources the Agents pool reads:
41
+ * `own` (id → its latest sign of work, epoch ms) — a turn in flight in this
42
+ * process, or a pool row still running or queued (worker index
43
+ * status/stage); `agents` — the Leads such an Agent session works for. */
44
+ async function storedLiveWork() {
45
+ const [
46
+ { inFlightSessionActivity },
47
+ { listStoredAgentWorkersAsync },
48
+ { hasPendingAgentWork, sessionsWithPendingWork },
49
+ ] = await Promise.all([
50
+ import('../runtime/agent/orchestrator/session/manager/runtime-state.mjs'),
51
+ import('../runtime/agent/orchestrator/session/store-agent-worker-rows.mjs'),
52
+ import('../runtime/shared/pending-agent-work.mjs'),
53
+ ]);
54
+ const { working, agentWorking } = inFlightSessionActivity();
55
+ const pool = await listStoredAgentWorkersAsync({ runningSessionIds: working });
56
+ const now = Date.now();
57
+ const own = new Map([...working].map((id) => [id, now]));
58
+ for (const row of pool) {
59
+ const id = String(row?.sessionId || '');
60
+ if (id && !own.has(id) && hasPendingAgentWork(row)) own.set(id, Date.parse(row.updatedAt) || 0);
49
61
  }
50
- const member = await host.member(sessionId, { to: args.to, prompt: args.prompt }).catch((error) => {
51
- throw new Error(`session: ${error?.message || error}`);
52
- });
53
- return { host, ...member };
62
+ return { own, agents: new Set([...agentWorking, ...sessionsWithPendingWork(pool)]) };
63
+ }
64
+
65
+ /** The catalog row of `sessionId` read by id, or null when the catalog would not list it. */
66
+ async function storedCatalogRow(sessionId) {
67
+ const { readStoredSessionCatalogRow } = await import(
68
+ '../runtime/agent/orchestrator/session/store-summary-reader.mjs'
69
+ );
70
+ return readStoredSessionCatalogRow(sessionId);
71
+ }
72
+
73
+ /** The stored title of `sessionId` (its session record), or null. */
74
+ async function storedTitle(sessionId) {
75
+ const { readStoredSessionTranscript } = await import(
76
+ '../runtime/agent/orchestrator/session/store-summary-reader.mjs'
77
+ );
78
+ return (await readStoredSessionTranscript(sessionId, { metadataOnly: true }))?.title || null;
54
79
  }
55
80
 
56
81
  /** Model-facing session operations over the same service used by desktop and TUI. */
@@ -58,81 +83,30 @@ export function createSessionControl({
58
83
  getSessionService,
59
84
  agentTasks = createAgentTaskSessions({ getSessionService }),
60
85
  botStatus = createBotStatus({ getSessionService }),
86
+ // Agent work a session handed off and still waits on (agent-task-sessions.mjs).
87
+ pendingWork = taskSessionPendingWork,
88
+ liveWork = storedLiveWork,
89
+ readRow = storedCatalogRow,
90
+ readTitle = storedTitle,
61
91
  }) {
62
- /**
63
- * Another session's workspace for the git tool's worktree operations
64
- * (session-runtime/workspace/worktree-tool.mjs): one workspace method with
65
- * one input, through the same configure / read lanes the desktop uses. No
66
- * `ownTurn` ever crosses: the target must be idle. A change (anything but
67
- * the menu read) needs the caller's trusted scope (`callerScope`, from
68
- * worktree-access.mjs worktreeCallerScope) to be an attended conversation.
69
- */
70
- async function worktreeControl(args, caller, callerScope) {
71
- const sessionId = typeof args.session_id === 'string' ? args.session_id.trim() : '';
72
- if (!SESSION_ID_PATTERN.test(sessionId)) throw new Error('session: session_id is required for action=worktree');
73
- if (sessionId === caller) throw new Error('session: worktree control must target another session');
74
- if (!WORKTREE_METHODS.has(args.method)) throw new Error(`session: unsupported worktree method "${args.method}"`);
75
- if (args.method !== 'getBranchMenu') assertWorktreeMutationAllowed(callerScope, `session: worktree ${args.method}`);
76
- const service = getSessionService();
77
- if (!service) throw new Error('session: session service is unavailable');
78
- if (args.method === 'getBranchMenu') {
79
- return (await service.readSession({ sessionId, action: 'getBranchMenu', args: [] })).value;
80
- }
81
- const result = await service.configureSession(
82
- { sessionId, action: args.method, args: [args.input ?? {}] },
83
- { revision: 1 }
84
- );
85
- return result.value;
86
- }
92
+ const worktreeControl = createWorktreeControl(getSessionService);
93
+ const routeControl = createRouteControl(getSessionService);
87
94
 
88
95
  return async function executeSessionControl(args = {}, context = {}) {
89
96
  const action = args.action;
90
- if (
91
- action !== 'list' &&
92
- action !== 'create' &&
93
- action !== 'bots' &&
94
- action !== 'worktree' &&
95
- !TARGET_ACTIONS.has(action)
96
- ) {
97
- throw new Error(`session: unsupported remote action "${action}"`);
98
- }
99
- const caller = context.callerSessionId;
100
- if (typeof caller !== 'string' || !SESSION_ID_PATTERN.test(caller)) {
101
- throw new Error('session: caller session is required');
102
- }
97
+ const caller = requestCaller(action, context);
103
98
  // `callerAgentId`: set only for an attended agent conversation (workspace-tools.mjs).
104
99
  if (action === 'create') return agentTasks.create(args, context);
105
100
  if (action === 'worktree') return worktreeControl(args, caller, context.callerScope);
101
+ // The setup tool's per-session route actions (authorized by the setup executor).
102
+ if (action === 'route') return routeControl(args, caller);
106
103
  // `callerScope`: the caller's schedule-access scope (workspace-tools.mjs).
107
104
  if (action === 'bots') return botStatus(args, context);
108
- const sessionId = typeof args.session_id === 'string' ? args.session_id.trim() : '';
109
- if (TARGET_ACTIONS.has(action) && !SESSION_ID_PATTERN.test(sessionId)) {
110
- throw new Error(`session: session_id is required for action=${action}`);
111
- }
112
- if (MUTATING_ACTIONS.has(action) && sessionId === caller) {
113
- throw new Error(`session: ${action} must target another session`);
114
- }
115
- const limit = args.limit ?? 25;
116
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
117
- throw new Error('session: limit must be an integer from 1 to 100');
118
- }
119
- const offset = args.offset ?? 0;
120
- const textOffset = args.text_offset ?? 0;
121
- if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(offset + limit)) {
122
- throw new Error('session: offset must be a nonnegative safe integer');
123
- }
124
- if (!Number.isSafeInteger(textOffset) || textOffset < 0) throw new Error('session: invalid text_offset');
125
- if (args.text_offset !== undefined && (args.offset === undefined || limit !== 1 || !args.item_id)) {
126
- throw new Error('session: text_offset requires offset, limit:1 and item_id from read');
127
- }
105
+ const { sessionId, limit, offset, textOffset } = targetRequest(args, action, caller);
128
106
  const service = getSessionService();
129
107
  if (!service) throw new Error('session: session service is unavailable');
130
108
  if (action === 'list') {
131
- for (const flag of ['include_agents', 'include_archived']) {
132
- if (args[flag] !== undefined && typeof args[flag] !== 'boolean') {
133
- throw new Error(`session: ${flag} must be a boolean`);
134
- }
135
- }
109
+ assertListFlags(args);
136
110
  // Scoped like bots (`callerScope`, bot-status.mjs): the human's sessions
137
111
  // list everything with previews, a bot (or its unattended run) lists the
138
112
  // same rows (so it can find a session to send to) but only its own carry
@@ -143,28 +117,38 @@ export function createSessionControl({
143
117
  // out, so a page is never short while more rows match.
144
118
  const own = scopedBot(context.callerScope);
145
119
  const archived = (await readSessionSidebarState())?.archived ?? {};
146
- const keep = (row) =>
147
- (args.include_agents === true || !isAgentOnlySession(row)) &&
148
- (args.include_archived === true || !Object.hasOwn(archived, row.id));
120
+ const keep = listRowFilter(args, archived);
121
+ // Live work is overlaid on every row: a catalog row keeps the status and
122
+ // recency of its last settled turn, so a long-running agent read `idle`
123
+ // and sorted by its first prompt. The first page lists the live rows
124
+ // first (newest sign of work first); a later page keeps catalog order,
125
+ // where such a row may show again (never skipped).
126
+ const live = await liveWork();
127
+ const withLive = liveRowOverlay(live);
128
+ // Each live session's row is read by id (wherever it sits in the
129
+ // catalog), filtered like the listing, and the newest `limit` lead.
130
+ const candidates = liveCandidates(live);
131
+ let pinned = [];
132
+ if (!args.cursor && candidates.length) {
133
+ pinned = pinnedLiveRows(await Promise.all(candidates.map((id) => readRow(id))), keep, withLive, limit);
134
+ }
135
+ const pinnedIds = new Set(pinned.map((row) => row.id));
136
+ const want = limit - pinned.length;
149
137
  // Bounded: past 10000 rows scanned with fewer than `limit` matching, the
150
138
  // page ends (possibly empty) with a nextCursor to continue from.
151
- const scan = await scanCatalog(service, keep, { cursor: args.cursor || null, want: limit, maxRows: 10_000 });
152
- const sessions = scan.rows.map((row) => (Object.hasOwn(archived, row.id) ? { ...row, archived: true } : row));
153
- return sessionListOutput({ sessions, nextCursor: scan.nextCursor }, limit, {
154
- rich: own ? (row) => sessionBotOf(row) === own : own !== null,
155
- });
139
+ const scan =
140
+ want > 0
141
+ ? await scanCatalog(service, (row) => !pinnedIds.has(row.id) && keep(row), {
142
+ cursor: args.cursor || null,
143
+ want,
144
+ maxRows: 10_000,
145
+ })
146
+ : { rows: [], nextCursor: TOP_CURSOR };
147
+ const sessions = listedSessions(pinned, scan.rows, withLive, archived);
148
+ return sessionListOutput({ sessions, nextCursor: scan.nextCursor }, limit, listOutputOptions(own, pinnedIds));
156
149
  }
157
150
  if (action === 'send') {
158
- if (typeof args.prompt !== 'string' || !args.prompt.trim()) {
159
- throw new Error('session: prompt is required for action=send');
160
- }
161
- // A room member's session is the room's to drive: a message goes to the
162
- // room, naming the member.
163
- if (roomMemberOfSession(sessionId)) {
164
- throw new Error(
165
- `session: ${sessionId} is a group-room member's session; send to its room (session_id ${roomSessionId(roomMemberOfSession(sessionId).roomId)}) with to: <member>`
166
- );
167
- }
151
+ assertSendRequest(args, sessionId);
168
152
  // A send to a room's log is a message to one member there (`to`, or a
169
153
  // leading @name), run by the desktop room host; the member's turn in
170
154
  // its member session is the turn that answers and reports.
@@ -186,32 +170,7 @@ export function createSessionControl({
186
170
  const prompt = handoffContent(room ? room.text : args.prompt, context.attachmentParts);
187
171
  const turnSessionId = room ? room.sessionId : sessionId;
188
172
  const from = { sessionId: caller, ...(context.callerAgentId ? { agentId: context.callerAgentId } : {}) };
189
- // The member's turn carries the send's hop (`transcriptMeta.hop`), so a
190
- // chain through the room counts on toward MAX_BOT_HOPS; its bot chain is
191
- // recorded under every submission id that turn may take.
192
- const submit = room
193
- ? async ({ id, onSettled, follow, transcriptMeta } = {}) => {
194
- rememberSubmissionBotChain(id, transcriptMeta?.botChain);
195
- const followed = follow
196
- ? (submissionId) => {
197
- rememberSubmissionBotChain(submissionId, transcriptMeta?.botChain);
198
- return follow(submissionId);
199
- }
200
- : null;
201
- return {
202
- accepted:
203
- (await room.host.ask(sessionId, {
204
- agentId: room.agentId,
205
- prompt,
206
- from,
207
- ...(id ? { id } : {}),
208
- ...(transcriptMeta?.hop > 0 ? { hop: transcriptMeta.hop } : {}),
209
- ...(onSettled ? { onSettled } : {}),
210
- ...(followed ? { follow: followed } : {}),
211
- })) === true,
212
- };
213
- }
214
- : null;
173
+ const submit = room ? roomSubmitter(room, sessionId, prompt, from) : null;
215
174
  const addressed = room ? { to: room.agentId } : {};
216
175
  let origin = await agentTasks.reportOrigin(turnSessionId, context.callerAgentId, caller);
217
176
  // `effort` is the menu's effort pick on the target: checked now against the
@@ -228,29 +187,15 @@ export function createSessionControl({
228
187
  const target = (await service.readSession({ sessionId, transcriptItemLimit: 1 })).full;
229
188
  if (!target) throw new Error(`session: ${sessionId} is unavailable`);
230
189
  const level = checkEffort(args.effort, target, await readModelCatalog(service, caller));
231
- // sessionOnly: the session's route changes, never the user's saved model settings.
232
- applyEffort = async () => {
233
- const applied = await service.configureSession(
234
- { sessionId, action: 'setEffort', args: [level, { sessionOnly: true }] },
235
- { revision: 1 }
236
- );
237
- if (applied?.value === false) {
238
- throw new Error(`session: ${sessionId} is busy with another command; effort not changed`);
239
- }
240
- };
190
+ applyEffort = effortApplier(service, sessionId, level);
241
191
  }
242
192
  // A send made in a bot-descended turn (`callerHop` > 0), or by a bot to
243
193
  // another session, extends the chain by one; a bot's own task session
244
194
  // only continues it. Refused past the limit, whatever the target.
245
195
  const turnHop = Number(context.callerHop) || 0;
246
- const chained = (hop) => {
247
- if (hop > MAX_BOT_HOPS) throw new Error(BOT_HOP_LIMIT_ERROR);
248
- return hop;
249
- };
250
196
  if (origin) {
251
- const hop = chained(origin.target === 'session' ? turnHop + 1 : turnHop);
252
- if (hop > 0) origin = { ...origin, hop };
253
- if (botChain.length) origin = { ...origin, botChain };
197
+ const hop = chainedHop(origin.target === 'session' ? turnHop + 1 : turnHop);
198
+ origin = chainedOrigin(origin, hop, botChain);
254
199
  // The task session a bot created acts as the sending conversation does
255
200
  // from now on (attended chat or run); another's session keeps its own.
256
201
  if (origin.target !== 'session') await rebindDelegation(sessionId, caller);
@@ -258,7 +203,7 @@ export function createSessionControl({
258
203
  const sent = await agentTasks.send(turnSessionId, prompt, origin, submit);
259
204
  return { sessionId, ...addressed, ...sent, ...(hop > 0 ? { hop } : {}) };
260
205
  }
261
- const hop = turnHop > 0 ? chained(turnHop + 1) : 0;
206
+ const hop = turnHop > 0 ? chainedHop(turnHop + 1) : 0;
262
207
  await applyEffort();
263
208
  const chainMeta = { ...(hop > 0 ? { hop } : {}), ...(botChain.length ? { botChain } : {}) };
264
209
  let accepted;
@@ -273,27 +218,12 @@ export function createSessionControl({
273
218
  });
274
219
  accepted = result.accepted === true;
275
220
  }
276
- return {
277
- sessionId,
278
- ...addressed,
279
- accepted,
280
- reportsBack: false,
281
- ...(accepted ? { reportsBackReason: NOT_BOT_REASON } : {}),
282
- ...(hop > 0 ? { hop } : {}),
283
- };
221
+ return unreportedSendResult(sessionId, addressed, accepted, hop);
284
222
  }
285
223
  if (action === 'cancel') {
286
224
  // A room's exchange is run by the desktop room host, so its stop (the UI
287
225
  // stop button) ends it; the log is not a session the service can abort.
288
- if (roomIdOfSession(sessionId)) {
289
- let host;
290
- try {
291
- host = agentRoomHost();
292
- } catch (error) {
293
- throw new Error(`session: cannot cancel ${sessionId}: ${error.message}`);
294
- }
295
- return { sessionId, aborted: host.stop(sessionId) === true };
296
- }
226
+ if (roomIdOfSession(sessionId)) return stopRoomExchange(sessionId);
297
227
  const result = await service.abortSession({ sessionId });
298
228
  return { sessionId, aborted: result.aborted === true };
299
229
  }
@@ -315,9 +245,12 @@ export function createSessionControl({
315
245
  });
316
246
  const snapshot = result.full;
317
247
  if (!snapshot) throw new Error(`session: ${sessionId} is unavailable`);
248
+ // The snapshot carries no title: it lives on the session record. Quoted
249
+ // like a listed title.
250
+ const title = await readTitle(sessionId);
318
251
  return {
319
252
  sessionId,
320
- title: snapshot.title ?? null,
253
+ title: title ? escapeHarnessTags(truncateToolText(title, SESSION_TITLE_LIMIT)) : null,
321
254
  cwd: snapshot.cwd ?? null,
322
255
  provider: snapshot.provider ?? null,
323
256
  model: snapshot.model ?? null,
@@ -325,6 +258,9 @@ export function createSessionControl({
325
258
  autoEffort: snapshot.autoEffort ?? null,
326
259
  fast: snapshot.fast ?? null,
327
260
  busy: snapshot.busy === true,
261
+ // A Lead idle between turns while its agents run is still working:
262
+ // their results wake it, so `busy` alone would read as stalled.
263
+ agentsWorking: (await pendingWork(sessionId)) === true,
328
264
  quotaResume: snapshot.quotaResume ?? null,
329
265
  };
330
266
  };
@@ -77,7 +77,12 @@ import { createStoredSessionViews } from './daemon-stored-session-views.mjs';
77
77
  import * as quotaResumeStore from '../runtime/shared/quota-resume-store.mjs';
78
78
  import { registerWorktreeCacheCleanups } from '../runtime/shared/worktrees/cache-cleanups.mjs';
79
79
  import { createInlineSessionRuntimeHost } from './session-runtime-inline-host.mjs';
80
- import { recoverScheduleResults, setAgentHomeLiveSurface } from '../session-runtime/agent-home-delivery.mjs';
80
+ import {
81
+ postBeforeAgentTurn,
82
+ recoverScheduleResults,
83
+ setAgentHomeLiveSurface,
84
+ } from '../session-runtime/agent-home-delivery.mjs';
85
+ import { setSessionTurnPrelude } from '../runtime/shared/session-turn-prelude.mjs';
81
86
  import { recoverRoutineDeliveries } from '../session-runtime/owned-automation-run.mjs';
82
87
  import { getStandaloneMemoryRuntime } from '../session-runtime/services/memory-runtime-proxy.mjs';
83
88
  import { registerTranscriptPurgeHook } from '../session-runtime/session-transcript-purge.mjs';
@@ -1017,6 +1022,9 @@ async function main() {
1017
1022
  options: {
1018
1023
  mode: 'prompt',
1019
1024
  kickoff: true,
1025
+ // Another bot or agent reported back: the runtime's turn, which the
1026
+ // prompt-cache warmer does not learn from (shared/prompt-origin.mjs).
1027
+ origin: 'agent-report',
1020
1028
  ...(id ? { id } : {}),
1021
1029
  ...(hop > 0 ? { transcriptMeta: { hop } } : {}),
1022
1030
  onSettled: (detail) => settle(String(detail?.status || 'failed')),
@@ -1029,7 +1037,10 @@ async function main() {
1029
1037
  (await sessionService.submitSession({ sessionId, prompt, options }))?.accepted === true,
1030
1038
  abort: (sessionId) => sessionService.abortSession({ sessionId }),
1031
1039
  });
1032
- // Mixy's 30-minute heartbeat check (mixy-heartbeat.mjs), on an unref'd timer.
1040
+ // A bot's next turn, whatever starts it, first reads the progress updates
1041
+ // queued for its thread (they wake nobody on their own).
1042
+ setSessionTurnPrelude(postBeforeAgentTurn);
1043
+ // Mixy's heartbeat check (user-set interval) (mixy-heartbeat.mjs), on an unref'd timer.
1033
1044
  import('../session-runtime/mixy-heartbeat.mjs')
1034
1045
  .then(({ startMixyHeartbeat }) =>
1035
1046
  startMixyHeartbeat({ getSessionService: () => sessionService, log: (line) => log(`heartbeat: ${line}`) })
@@ -1041,6 +1052,18 @@ async function main() {
1041
1052
  // Agents' sends whose report an earlier process had not queued yet.
1042
1053
  agentTasks.recover().catch((error) => log(`agent task report recovery failed: ${error?.message || error}`));
1043
1054
  recoverRoutineDeliveries().catch((error) => log(`routine delivery recovery failed: ${error?.message || error}`));
1055
+ // Compaction-archive segments an earlier process could not publish.
1056
+ import('../runtime/agent/orchestrator/session/compaction-archive-capture.mjs')
1057
+ .then(({ recoverPendingCompactionArchives }) => recoverPendingCompactionArchives())
1058
+ .then((pending) => pending && log(`compaction archive: publishing pending segments of ${pending} session(s)`))
1059
+ .catch((error) => log(`compaction archive recovery failed: ${error?.message || error}`));
1060
+ // Prompt-cache warms toward routine fires (session/cache-warm.mjs) lived in
1061
+ // the previous process: re-arm them from their durable cache touches (the
1062
+ // re-arm waits for the providers to be registered).
1063
+ import('../runtime/agent/orchestrator/session/cache-warm.mjs')
1064
+ .then(({ rearmCacheWarmsAfterRestart }) => rearmCacheWarmsAfterRestart())
1065
+ .then((armed) => armed && log(`cache warm: re-armed ${armed} session(s)`))
1066
+ .catch((error) => log(`cache warm re-arm failed: ${error?.message || error}`));
1044
1067
  sessionTransport = createSessionTransport({
1045
1068
  // ctx carries the CLIENT token: the session service refcounts views across
1046
1069
  // processes with it, so a terminal exiting cannot destroy the session a
@@ -1096,7 +1119,7 @@ async function main() {
1096
1119
 
1097
1120
  // A pinned channel session outlives the daemon that pinned it. Reactivate the
1098
1121
  // durable intent AFTER the ready handshake (activation itself brings the
1099
- // messaging bridge up); with no intent on disk this is a no-op and the
1122
+ // channel bridge up); with no intent on disk this is a no-op and the
1100
1123
  // channels graph stays dormant.
1101
1124
  if (transport.remoteIntentSessionId) {
1102
1125
  void transport
@@ -274,7 +274,9 @@ async function replaceLowerDaemon(discovery, initialHealth, { log, onUpgradeWait
274
274
  const notify = (state) => {
275
275
  try {
276
276
  onUpgradeWait?.({ state, ...info });
277
- } catch {}
277
+ } catch {
278
+ // A throwing progress callback must not abort the upgrade wait.
279
+ }
278
280
  };
279
281
  const done = () => {
280
282
  notify('done');
@@ -376,7 +378,8 @@ export function spawnDaemonCandidate({ cwd, log, timeoutMs = 30_000, entry = dae
376
378
  }
377
379
 
378
380
  /** Spawn-or-attach discovery for the machine-global daemon.
379
- * `entry` is a test seam; production always forks the real daemon. */
381
+ * `entry` and `proveOwner` are test seams; production always forks the real
382
+ * daemon and probes the real owner. */
380
383
  export async function ensureDaemon({
381
384
  cwd = process.cwd(),
382
385
  log = () => {},
@@ -384,6 +387,7 @@ export async function ensureDaemon({
384
387
  readyTimeoutMs = null,
385
388
  onUpgradeWait = null,
386
389
  entry = daemonEntry(),
390
+ proveOwner = proveDaemonOwnerRecord,
387
391
  } = {}) {
388
392
  const configuredTimeoutMs = Number(process.env.MIXDOG_DAEMON_READY_TIMEOUT_MS);
389
393
  let requestedTimeoutMs = DEFAULT_DAEMON_READY_TIMEOUT_MS;
@@ -460,7 +464,7 @@ export async function ensureDaemon({
460
464
  // answer with its pid — a crashed daemon leaves its files behind, naming a
461
465
  // pid the OS may have reused. A probe cut short by this deadline proves
462
466
  // nothing either way.
463
- const proof = await proveDaemonOwnerRecord(daemonOwnerPath(), {
467
+ const proof = await proveOwner(daemonOwnerPath(), {
464
468
  discoveryPath: sessionDiscoveryPath(),
465
469
  budgetMs: Math.max(1, deadline - Date.now()),
466
470
  });
@@ -27,6 +27,7 @@ export const SESSION_READ_ACTIONS = Object.freeze([
27
27
  'getChannelSettings',
28
28
  'getChannelSetup',
29
29
  'getChannelWorkerStatus',
30
+ 'getCacheWarmingSettings',
30
31
  'getCompactionSettings',
31
32
  'getConversationImportJobs',
32
33
  'getDeveloperSettings',
@@ -40,6 +41,7 @@ export const SESSION_READ_ACTIONS = Object.freeze([
40
41
  'getOAuthProviderLoginStatus',
41
42
  'getOutputStyle',
42
43
  'getOrchestrationMode',
44
+ 'getConfiguredOrchestrationMode',
43
45
  'getProfile',
44
46
  'getProviderSetup',
45
47
  'getProviderAccounts',
@@ -211,6 +213,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
211
213
  'setAgentRoute',
212
214
  'setAutoClear',
213
215
  'setAutoUpdate',
216
+ 'setCacheWarmingEnabled',
214
217
  'setCompactionSettings',
215
218
  'setCwd',
216
219
  'setDeveloperOption',
@@ -242,6 +245,8 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
242
245
  'setRecapEnabled',
243
246
  'setAiSessionTitles',
244
247
  'setHeartbeatEnabled',
248
+ 'setHeartbeatInterval',
249
+ 'noteUserInput',
245
250
  'setReactionsEnabled',
246
251
  'setLeadAppearance',
247
252
  'setBotOrder',
@@ -31,6 +31,7 @@ export function createAgentChildFactory({ registry, rehydrateAgentSessions, crea
31
31
  sourceName: spec.sourceName || spec.agent || 'agent',
32
32
  clientHostPid: spec.clientHostPid || null,
33
33
  ...(preset.fallbacks !== undefined ? { routeFallbacks: preset.fallbacks } : {}),
34
+ ...(preset.fallbackSource ? { routeFallbackSource: preset.fallbackSource } : {}),
34
35
  };
35
36
  const created = await createSession({
36
37
  cwd: spec.cwd || process.cwd(),
@@ -44,6 +44,9 @@ async function resumeDueEntry(entry, { store, materializeSession, readQuotaUsabl
44
44
  try {
45
45
  usable = await readQuotaUsable(provider, model);
46
46
  } catch (error) {
47
+ // A disconnected PC supplies no quota reading. The scheduler's outer
48
+ // deferral keeps this reservation without spending an exhaustion attempt.
49
+ if (error?.code === 'MODEL_PROXY_UNAVAILABLE') throw error;
47
50
  log(`quota resume usage reading failed provider=${provider}: ${error?.message || error}`);
48
51
  }
49
52
  if (!isCurrent(store, entry)) return 'cancelled';
@@ -7,6 +7,17 @@
7
7
  * turn. The eviction sweep that consumes this lives in ../retention.mjs.
8
8
  */
9
9
  import { activeBackgroundTaskSessionIds, hasActiveBackgroundTasks } from '../../../runtime/shared/background-tasks.mjs';
10
+ import { emitSessionApprovalCardChange } from '../../../runtime/shared/session-activity-signal.mjs';
11
+ import { roomMemberOfSession } from '../../../runtime/agent/orchestrator/session/agent-room.mjs';
12
+
13
+ /** A room member session's copies of the questions of sessions it handed work
14
+ * to (tui/session/tool-approval.mjs `handoffQuestions`); empty for any other
15
+ * session. The room shows them from the member's catalog row (agent-rooms.ts),
16
+ * as no view watches a member session. */
17
+ function roomHandoffQuestionsOf(sessionId, state) {
18
+ if (!roomMemberOfSession(sessionId)) return [];
19
+ return Array.isArray(state?.handoffQuestions) ? state.handoffQuestions : [];
20
+ }
10
21
 
11
22
  export function createBusyTracker({ sessions, currentSessionId }) {
12
23
  function stateBusy(state) {
@@ -18,9 +29,54 @@ export function createBusyTracker({ sessions, currentSessionId }) {
18
29
  function updateEntryBusy(entry, state) {
19
30
  const next = stateBusy(state);
20
31
  entry.busy = next;
32
+ // Every runtime state change passes here, watched or headless: a session
33
+ // that starts or stops showing an approval card (any kind, a sub-agent's
34
+ // delegated copy included) announces it, so the session catalog — the one
35
+ // status source every list row has, open pane or not — re-reads it.
36
+ const awaitingApproval = Boolean(state?.toolApproval);
37
+ const awaitingQuestion = state?.toolApproval?.kind === 'question';
38
+ // A room member's handed-off questions coming and going re-read it too.
39
+ const sessionId = currentSessionId(entry) || '';
40
+ const handoffQuestions = roomHandoffQuestionsOf(sessionId, state)
41
+ .map((question) => String(question?.id ?? ''))
42
+ .join('\n');
43
+ if (
44
+ awaitingApproval !== (entry.awaitingApproval === true) ||
45
+ awaitingQuestion !== (entry.awaitingQuestion === true) ||
46
+ handoffQuestions !== (entry.handoffQuestions ?? '')
47
+ ) {
48
+ entry.awaitingApproval = awaitingApproval;
49
+ entry.awaitingQuestion = awaitingQuestion;
50
+ entry.handoffQuestions = handoffQuestions;
51
+ emitSessionApprovalCardChange(sessionId);
52
+ }
21
53
  return next;
22
54
  }
23
55
 
56
+ /** Room member session id -> its pending handed-off question copies, for
57
+ * every live member runtime holding one. */
58
+ function roomHandoffQuestions() {
59
+ const byMember = new Map();
60
+ for (const entry of sessions) {
61
+ const sessionId = currentSessionId(entry);
62
+ const questions = sessionId ? roomHandoffQuestionsOf(sessionId, entry.runtime?.getState?.()) : [];
63
+ if (questions.length) byMember.set(sessionId, questions);
64
+ }
65
+ return byMember;
66
+ }
67
+
68
+ /** Session id -> the kind of its pending card ('approval' | 'question') for
69
+ * every session whose live runtime shows one waiting on the user. */
70
+ function awaitingApprovalSessionKinds() {
71
+ const kinds = new Map();
72
+ for (const entry of sessions) {
73
+ const sessionId = currentSessionId(entry);
74
+ const pending = sessionId ? entry.runtime?.getState?.()?.toolApproval : null;
75
+ if (pending) kinds.set(sessionId, pending.kind === 'question' ? 'question' : 'approval');
76
+ }
77
+ return kinds;
78
+ }
79
+
24
80
  function sessionBusy(entry) {
25
81
  const sessionId = currentSessionId(entry);
26
82
  // Detached views do not make their background commands disposable. Keep
@@ -65,5 +121,13 @@ export function createBusyTracker({ sessions, currentSessionId }) {
65
121
  return count;
66
122
  }
67
123
 
68
- return { stateBusy, updateEntryBusy, sessionBusy, liveBusyCount, liveTurnBusyCount };
124
+ return {
125
+ stateBusy,
126
+ updateEntryBusy,
127
+ awaitingApprovalSessionKinds,
128
+ roomHandoffQuestions,
129
+ sessionBusy,
130
+ liveBusyCount,
131
+ liveTurnBusyCount,
132
+ };
69
133
  }