mixdog 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/provider-pingpong-live.mjs +509 -0
  4. package/src/agents/advisor/AGENT.md +1 -1
  5. package/src/agents/advisor/agent.json +1 -1
  6. package/src/defaults/shipped-history.json +5 -0
  7. package/src/defaults/skills/setup/SKILL.md +6 -1
  8. package/src/defaults/skills/setup/references/actions.md +44 -22
  9. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  10. package/src/headless-exec.mjs +15 -8
  11. package/src/lib/keychain-cjs.cjs +7 -4
  12. package/src/output-styles/common.md +11 -10
  13. package/src/rules/agent/AGENT.md +5 -5
  14. package/src/rules/lead/LEAD.md +5 -5
  15. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  16. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  17. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  19. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  21. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  22. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  23. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +2 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +7 -6
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -10
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  31. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +58 -7
  39. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
  40. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +16 -3
  41. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  42. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +12 -6
  43. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +114 -72
  45. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +37 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  50. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +3 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor.mjs +9 -2
  52. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  53. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  54. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  55. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  56. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  57. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  59. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +25 -8
  60. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +87 -5
  61. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  62. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +13 -13
  63. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +355 -37
  64. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
  65. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +93 -47
  66. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +55 -10
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +26 -8
  68. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  71. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  72. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  73. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  74. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  75. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  76. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  77. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  78. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  80. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  81. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  82. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  83. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +121 -61
  89. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  90. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
  91. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  92. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  93. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +42 -24
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +53 -5
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
  108. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  109. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
  110. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  111. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +25 -4
  112. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  114. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
  119. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
  121. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +71 -24
  122. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  123. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  124. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  125. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  126. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  127. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  130. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +11 -3
  131. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  132. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  133. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  134. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +147 -8
  135. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  136. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +5 -0
  137. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  138. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  139. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  140. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  141. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  142. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  143. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  147. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  148. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  150. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  151. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  154. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
  156. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  157. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  158. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  159. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  160. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  161. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  162. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  163. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  164. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  165. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  167. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  168. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  169. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  170. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  171. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  172. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +63 -20
  173. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +2 -1
  174. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  175. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  179. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +4 -1
  180. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  181. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +16 -0
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  184. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  185. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  187. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  188. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +15 -3
  189. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  190. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  192. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  193. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  195. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  196. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -0
  197. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  198. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  199. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  200. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  201. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  202. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  203. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  204. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  205. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  206. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  207. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  208. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
  209. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
  210. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  211. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  212. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  213. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  214. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  216. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  217. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  218. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  223. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  224. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  225. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  226. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  227. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  228. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  229. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  230. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  231. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  232. package/src/runtime/github/tool.mjs +92 -2
  233. package/src/runtime/memory/index.mjs +1 -0
  234. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  235. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  236. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  237. package/src/runtime/memory/lib/pg/process.mjs +31 -26
  238. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +108 -3
  240. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  241. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  242. package/src/runtime/office/README.md +1 -1
  243. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  244. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  245. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  246. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  247. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  248. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  249. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  250. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  251. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  252. package/src/runtime/office/core/office-sessions.mjs +5 -0
  253. package/src/runtime/office/core/office-transactions.mjs +3 -0
  254. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  255. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  256. package/src/runtime/office/index.mjs +30 -0
  257. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  258. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  259. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  260. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  261. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  262. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  263. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  264. package/src/runtime/shared/buffered-appender.mjs +18 -0
  265. package/src/runtime/shared/config.mjs +78 -18
  266. package/src/runtime/shared/err-text.mjs +6 -0
  267. package/src/runtime/shared/file-permissions.d.mts +6 -1
  268. package/src/runtime/shared/file-permissions.mjs +54 -6
  269. package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
  270. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  271. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  272. package/src/runtime/shared/loopback-listener.mjs +9 -0
  273. package/src/runtime/shared/provider-api-key.mjs +2 -0
  274. package/src/runtime/shared/review-diff.mjs +52 -0
  275. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  276. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  277. package/src/runtime/shared/slow-span.mjs +114 -0
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  279. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  280. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  281. package/src/runtime/shared/tool-card-model.mjs +5 -2
  282. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  283. package/src/runtime/shared/tool-primitives.mjs +13 -0
  284. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  285. package/src/runtime/shared/tool-surface.mjs +18 -3
  286. package/src/runtime/shared/tool-work-units.mjs +40 -4
  287. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  288. package/src/runtime/shared/transcript-writer.mjs +10 -2
  289. package/src/runtime/shared/turn-changes.mjs +268 -100
  290. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  291. package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
  292. package/src/runtime/shared/turn-snapshot.mjs +857 -683
  293. package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
  294. package/src/runtime/shared/user-data-guard.mjs +262 -49
  295. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  296. package/src/runtime/shared/worktrees/contract.mjs +34 -0
  297. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  298. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  299. package/src/runtime/shared/worktrees/git.mjs +8 -3
  300. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  301. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  302. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  303. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  304. package/src/runtime/terminal-bridge/client.mjs +44 -11
  305. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  306. package/src/runtime/tidy/apply.mjs +47 -2
  307. package/src/runtime/tidy/run-engines.mjs +11 -5
  308. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  309. package/src/session-runtime/boot/apis.mjs +13 -5
  310. package/src/session-runtime/boot/facade.mjs +1 -0
  311. package/src/session-runtime/boot/infrastructure.mjs +6 -1
  312. package/src/session-runtime/boot/session.mjs +0 -1
  313. package/src/session-runtime/boot/tools.mjs +4 -1
  314. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  315. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  316. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  317. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  318. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  319. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  320. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  321. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  322. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -3
  323. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  324. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  325. package/src/session-runtime/lifecycle/teardown.mjs +72 -7
  326. package/src/session-runtime/lifecycle-api.mjs +1 -0
  327. package/src/session-runtime/mcp-glue/connect.mjs +17 -5
  328. package/src/session-runtime/model-route/set-route.mjs +2 -2
  329. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  330. package/src/session-runtime/native-web-search.mjs +10 -1
  331. package/src/session-runtime/new-session-config.mjs +4 -2
  332. package/src/session-runtime/provider-auth/login.mjs +4 -0
  333. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  334. package/src/session-runtime/runtime-review-api.mjs +8 -2
  335. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  336. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  337. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  338. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  339. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  340. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  341. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  342. package/src/session-runtime/services/agent-tool.mjs +3 -0
  343. package/src/session-runtime/services/channel-admin.mjs +19 -3
  344. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  345. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  346. package/src/session-runtime/services/provider-admin.mjs +81 -15
  347. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  348. package/src/session-runtime/session-turn-api.mjs +14 -5
  349. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  350. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  351. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  352. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  353. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  354. package/src/session-runtime/skills-api.mjs +3 -5
  355. package/src/session-runtime/tool-surface.mjs +5 -1
  356. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  357. package/src/session-runtime/turn/session-ops.mjs +7 -5
  358. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  359. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  360. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  361. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  362. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  363. package/src/session-runtime/turn/turn-run.mjs +25 -9
  364. package/src/session-runtime/usage-stats-api.mjs +43 -11
  365. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  366. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  367. package/src/session-runtime/workspace/transitions.mjs +190 -15
  368. package/src/standalone/daemon-agent-control.mjs +9 -1
  369. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  370. package/src/standalone/daemon-owner.mjs +17 -4
  371. package/src/standalone/daemon-pg-stop.mjs +51 -0
  372. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  373. package/src/standalone/daemon-stop-budget.mjs +18 -0
  374. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  375. package/src/standalone/daemon-telemetry.mjs +19 -0
  376. package/src/standalone/daemon.mjs +61 -1
  377. package/src/standalone/desktop-service-registry.mjs +5 -0
  378. package/src/standalone/local-session-runtime.mjs +70 -19
  379. package/src/standalone/session-client.mjs +42 -0
  380. package/src/standalone/session-protocol.mjs +7 -0
  381. package/src/standalone/session-proxy/projection.mjs +41 -4
  382. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  383. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  384. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  385. package/src/standalone/session-service-api.mjs +6 -1
  386. package/src/standalone/session-state-patch.d.mts +2 -1
  387. package/src/standalone/session-state-patch.mjs +41 -1
  388. package/src/tui/App.jsx +25 -0
  389. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  390. package/src/tui/app/btw-controller.mjs +118 -0
  391. package/src/tui/app/create-app-pickers.mjs +2 -0
  392. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  393. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  394. package/src/tui/app/slash-commands.mjs +13 -3
  395. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  396. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  397. package/src/tui/app/use-global-key-input.mjs +5 -12
  398. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  399. package/src/tui/components/BtwPanel.jsx +53 -0
  400. package/src/tui/components/QuestionPanel.jsx +275 -0
  401. package/src/tui/components/question-panel-model.mjs +156 -0
  402. package/src/tui/dist/index.mjs +2413 -1465
  403. package/src/tui/session/context-state.mjs +4 -2
  404. package/src/tui/session/session-api/commands.mjs +10 -0
  405. package/src/tui/session/session-api/controls.mjs +2 -2
  406. package/src/tui/session/session-api/integrations.mjs +9 -2
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  408. package/src/tui/session/session-api/lifecycle.mjs +2 -1
  409. package/src/tui/session/session-api/settings.mjs +6 -0
  410. package/src/tui/session/session-api.mjs +1 -0
  411. package/src/tui/session/tool-approval.mjs +10 -7
  412. package/src/tui/session/transcript-spill.mjs +14 -7
  413. package/src/tui/session/turn-stream.mjs +174 -31
  414. package/src/tui/session/turn.mjs +12 -5
  415. package/src/workflows/default/WORKFLOW.md +3 -1
@@ -54,12 +54,14 @@ export function createProjectBranchOps({ backend, inspectProject, findRegistered
54
54
  const path = await registeredProject(projectPath);
55
55
  const project = await inspectProject(path);
56
56
  const menu = await (await backend()).listBranchOccupancy({ projectPath: path, sessionId: null });
57
+ const workspace = projectWorkspace({
58
+ projectPath: path,
59
+ repoCommonDir: project.repoCommonDir,
60
+ branch: project.currentBranch,
61
+ });
57
62
  return {
58
- workspace: projectWorkspace({
59
- projectPath: path,
60
- repoCommonDir: project.repoCommonDir,
61
- branch: project.currentBranch,
62
- }),
63
+ // Detached: branch null and the checked-out commit in baseCommit.
64
+ workspace: project.currentBranch ? workspace : { ...workspace, baseCommit: project.headCommit ?? null },
63
65
  branches: branchMenuEntries(menu.branches, menu.occupancy),
64
66
  occupancy: menu.occupancy,
65
67
  retainedWorktree: null,
@@ -18,7 +18,9 @@ import { checkoutFolder, projectSubdirOf, samePath } from '../../runtime/shared/
18
18
  import { setWorkspaceCwdLock, workspaceCwdLock } from './admission-gate.mjs';
19
19
  import { copyProjectIdentity } from './workspace-state.mjs';
20
20
 
21
- const PROMPT_FIELDS = ['bp3EnvironmentContext', 'bp3CoreContext', 'sessionStartMetaInjected'];
21
+ // workspaceReroot: the staged working-folder change and its pending notice
22
+ // (prompt-utils stageWorkspaceReroot); a rollback drops the notice with it.
23
+ const PROMPT_FIELDS = ['bp3EnvironmentContext', 'bp3CoreContext', 'sessionStartMetaInjected', 'workspaceReroot'];
22
24
 
23
25
  /** Everything a prompt refresh rewrites on `target`, restorable exactly. */
24
26
  function snapshotPrompt(target) {
@@ -90,6 +92,8 @@ export function createTransitionSteps({
90
92
  inspectProject,
91
93
  runtimeToken,
92
94
  getCurrentCwd,
95
+ getDesktopSession,
96
+ setDesktopSession,
93
97
  applyResolvedCwd,
94
98
  persistSession,
95
99
  refreshWorkspaceContext,
@@ -97,11 +101,19 @@ export function createTransitionSteps({
97
101
  updateLeadWorkerCwd,
98
102
  workersOf,
99
103
  withProjectFolder,
104
+ assertCheckoutQuiet = async () => {},
100
105
  }) {
101
106
  const owner = (sessionId) => ({ sessionId, runtimeToken, pid: process.pid });
107
+ const moveLeadRow = (sessionId, cwd) => {
108
+ try {
109
+ updateLeadWorkerCwd(sessionId, cwd);
110
+ } catch {
111
+ /* the Lead pool must never break a transition */
112
+ }
113
+ };
102
114
 
103
115
  /** Point this Lead's idle workers at the new root so a reused worker never keeps an old workspace. */
104
- async function retargetWorkers(session, cwd, journal) {
116
+ async function retargetWorkers(session, cwd, journal, move = null) {
105
117
  for (const worker of workersOf(session)) {
106
118
  if (samePath(worker.cwd || '', cwd)) continue;
107
119
  const priorCwd = worker.cwd;
@@ -113,7 +125,7 @@ export function createTransitionSteps({
113
125
  persistSession(worker);
114
126
  });
115
127
  worker.cwd = cwd;
116
- await refreshWorkspaceContext(worker, cwd);
128
+ await refreshWorkspaceContext(worker, cwd, move);
117
129
  worker.updatedAt = Date.now();
118
130
  persistSession(worker);
119
131
  }
@@ -123,16 +135,10 @@ export function createTransitionSteps({
123
135
  * Apply the new workspace: runtime cwd, record, worker lock, prompt context
124
136
  * of the Lead and its idle workers, persistence. Journals the exact undo
125
137
  * (cwd, record, lock, prompts, worker roots) for a transaction that fails
126
- * after this point.
138
+ * after this point. `move` ({ from, changes }: a move out of the project
139
+ * folder into a worktree) extends the prompt's working-folder notice.
127
140
  */
128
- async function commit(session, previous, fields, journal) {
129
- const moveLeadRow = (cwd) => {
130
- try {
131
- updateLeadWorkerCwd(session.id, cwd);
132
- } catch {
133
- /* the Lead pool must never break a transition */
134
- }
135
- };
141
+ async function commit(session, previous, fields, journal, move = null) {
136
142
  const next = normalizeSessionWorkspace({
137
143
  ...fields,
138
144
  revision: nextRevision(previous),
@@ -153,22 +159,70 @@ export function createTransitionSteps({
153
159
  prior.prompt();
154
160
  session.updatedAt = Date.now();
155
161
  persistSession(session);
156
- moveLeadRow(prior.sessionCwd || prior.cwd);
162
+ moveLeadRow(session.id, prior.sessionCwd || prior.cwd);
157
163
  invalidateCaches();
158
164
  });
159
165
  applyResolvedCwd(next.cwd);
160
166
  session.cwd = next.cwd;
161
167
  session.workspace = next;
162
- moveLeadRow(next.cwd);
168
+ moveLeadRow(session.id, next.cwd);
163
169
  setWorkspaceCwdLock(session.id, isWorktreeBound(next) ? next.cwd : null);
164
- await refreshWorkspaceContext(session, next.cwd);
165
- await retargetWorkers(session, next.cwd, journal);
170
+ await refreshWorkspaceContext(session, next.cwd, move);
171
+ await retargetWorkers(session, next.cwd, journal, move);
166
172
  session.updatedAt = Date.now();
167
- persistSession(session);
173
+ // Awaited: a failed save fails the transaction (and its journal undoes it).
174
+ await persistSession(session);
168
175
  invalidateCaches();
169
176
  return { revision: next.revision, workspace: next };
170
177
  }
171
178
 
179
+ /**
180
+ * A Project switch (session set-project, the desktop/remote setCwd): the
181
+ * runtime cwd, session.cwd, workspace record and desktop classification
182
+ * move together (applyResolvedCwd with persistProjectSelection), then the
183
+ * prompt context of the Lead and its idle workers follows, awaited, before
184
+ * the switch completes. Journals the exact undo, so a failed refresh puts
185
+ * everything back and the failure reaches the caller.
186
+ */
187
+ async function switchProject(session, cwd, journal, { classifyByMembership = false } = {}) {
188
+ const sessionFields = ['cwd', 'workspace', 'desktopSession'].map((name) => [
189
+ name,
190
+ Object.hasOwn(session, name),
191
+ session[name],
192
+ ]);
193
+ const prior = {
194
+ cwd: getCurrentCwd(),
195
+ desktop: getDesktopSession(),
196
+ lock: workspaceCwdLock([session.id]),
197
+ prompt: snapshotPrompt(session),
198
+ };
199
+ journal.push(() => {
200
+ applyResolvedCwd(prior.cwd);
201
+ for (const [name, had, value] of sessionFields) {
202
+ if (had) session[name] = value;
203
+ else delete session[name];
204
+ }
205
+ setDesktopSession(prior.desktop);
206
+ setWorkspaceCwdLock(session.id, prior.lock);
207
+ prior.prompt();
208
+ session.updatedAt = Date.now();
209
+ persistSession(session);
210
+ moveLeadRow(session.id, session.cwd || prior.cwd);
211
+ invalidateCaches();
212
+ });
213
+ const changed = !samePath(prior.cwd || '', cwd);
214
+ applyResolvedCwd(cwd, { persistProjectSelection: true, classifyByMembership });
215
+ if (changed) {
216
+ moveLeadRow(session.id, cwd);
217
+ await refreshWorkspaceContext(session, cwd);
218
+ await retargetWorkers(session, cwd, journal);
219
+ session.updatedAt = Date.now();
220
+ persistSession(session);
221
+ }
222
+ invalidateCaches();
223
+ return { cwd: getCurrentCwd(), workspace: session.workspace ?? null };
224
+ }
225
+
172
226
  async function releaseOwner(backendApi, entryId, sessionId) {
173
227
  try {
174
228
  await backendApi.releaseWorktreeOwner(entryId, owner(sessionId));
@@ -178,7 +232,14 @@ export function createTransitionSteps({
178
232
  }
179
233
  }
180
234
 
181
- async function enterWorktree(session, previous, { name, nameHint, confirmLarge, signal, txId, journal }) {
235
+ /**
236
+ * `changes` (a new worktree only): the project folder's uncommitted
237
+ * changes — `ask` refuses with WORKTREE_DIRTY_CHOICE while there are any,
238
+ * `leave` keeps them there, `carry` moves them into the worktree while the
239
+ * project folder is held (refused while other sessions work in it);
240
+ * omitted, they are not examined (lazy provisioning).
241
+ */
242
+ async function enterWorktree(session, previous, { name, nameHint, confirmLarge, changes, signal, txId, journal }) {
182
243
  const backendApi = await backend();
183
244
  const projectPath = previous.projectPath;
184
245
  const project = await inspectProject(projectPath, { signal });
@@ -197,16 +258,39 @@ export function createTransitionSteps({
197
258
  // The backend resolves the base branch commit (from the project
198
259
  // folder's checked-out branch), sizes and creates the checkout; the
199
260
  // txId makes a retried or duplicate send return the same worktree.
200
- entry = await backendApi.createManagedWorktree({
201
- projectPath,
202
- sessionId: session.id,
203
- runtimeToken,
204
- baseBranch: project.currentBranch,
205
- nameHint: name ?? nameHint ?? '',
206
- confirmLarge: confirmLarge === true,
207
- txId,
208
- signal,
209
- });
261
+ const create = () =>
262
+ backendApi.createManagedWorktree({
263
+ projectPath,
264
+ sessionId: session.id,
265
+ runtimeToken,
266
+ baseBranch: project.currentBranch,
267
+ nameHint: name ?? nameHint ?? '',
268
+ confirmLarge: confirmLarge === true,
269
+ txId,
270
+ signal,
271
+ carryChanges: changes ?? null,
272
+ });
273
+ // A carry clears the project folder: it is held, and every session
274
+ // sharing it must be quiet (no turn, agents or background shells).
275
+ entry =
276
+ changes === 'carry'
277
+ ? await withProjectFolder(session.id, project.topLevel || projectPath, async () => {
278
+ await assertCheckoutQuiet(session, project.topLevel || projectPath);
279
+ return create();
280
+ })
281
+ : await create();
282
+ const recoveryRef = entry.changes?.recoveryRef;
283
+ if (recoveryRef) {
284
+ // Anything that fails before the move is committed puts the changes
285
+ // back into the project folder and drops the worktree made for them;
286
+ // a restore that is refused keeps both (and the recovery ref).
287
+ const created = entry;
288
+ journal.push(async () => {
289
+ await backendApi.restoreCarriedChanges({ projectPath, recoveryRef });
290
+ await backendApi.removeManagedWorktree({ entryId: created.id, force: true });
291
+ await backendApi.dropCarryRecoveryRef({ projectPath, recoveryRef });
292
+ });
293
+ }
210
294
  }
211
295
  // Cancelled after the worktree exists: it stays retained (same txId on
212
296
  // retry), the session does not move.
@@ -228,10 +312,24 @@ export function createTransitionSteps({
228
312
  baseCommit: entry.baseCommit,
229
313
  mergeTarget: project.currentBranch,
230
314
  },
231
- journal
315
+ journal,
316
+ // The tail notice: what happened to the uncommitted changes, and how
317
+ // the project-folder paths of the conversation so far map to the worktree.
318
+ isWorktreeBound(previous) ? null : { from: previous.cwd, changes: entry.changes?.mode ?? null }
232
319
  );
320
+ // The move is committed and persisted: the carried changes' recovery ref
321
+ // has done its job (a failure to drop it only leaves a spare ref).
322
+ if (entry.changes?.recoveryRef) {
323
+ await backendApi
324
+ .dropCarryRecoveryRef({ projectPath, recoveryRef: entry.changes.recoveryRef })
325
+ .catch(() => {});
326
+ }
233
327
  // Creation notes (submodules, include entries missing or already present).
234
- return { ...entered, warnings: Array.isArray(entry.warnings) ? [...entry.warnings] : [] };
328
+ return {
329
+ ...entered,
330
+ warnings: Array.isArray(entry.warnings) ? [...entry.warnings] : [],
331
+ ...(entry.changes ? { changes: { mode: entry.changes.mode, count: entry.changes.count } } : {}),
332
+ };
235
333
  }
236
334
 
237
335
  async function enterProjectBranch(session, previous, { branch, create, signal, journal }) {
@@ -360,5 +458,5 @@ export function createTransitionSteps({
360
458
  return { ...result, removed };
361
459
  }
362
460
 
363
- return { enterWorktree, enterProjectBranch, removeRetainedWorktree };
461
+ return { enterWorktree, enterProjectBranch, removeRetainedWorktree, switchProject };
364
462
  }
@@ -12,7 +12,9 @@
12
12
  * (runtime/shared/worktrees/manager.mjs), loaded on first use.
13
13
  */
14
14
  import { randomUUID } from 'node:crypto';
15
+ import { isDeepStrictEqual } from 'node:util';
15
16
  import {
17
+ WORKTREE_CARRY_CHOICES,
16
18
  branchMenuEntries,
17
19
  isWorktreeBound,
18
20
  isWorktreeError,
@@ -24,6 +26,7 @@ import { normalizeWorktreeSettings } from '../../runtime/shared/worktrees/settin
24
26
  import * as maintenance from '../../runtime/shared/worktrees/maintenance.mjs';
25
27
  import { inspectRepo, throwIfAborted } from '../../runtime/shared/worktrees/git.mjs';
26
28
  import { isWithin, samePath } from '../../runtime/shared/worktrees/paths.mjs';
29
+ import { readGitHead } from '../../runtime/shared/worktrees/git-head.mjs';
27
30
  import { sessionHasConversationMessages } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
28
31
  import { buildWorkspacePromptContext } from '../../runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs';
29
32
  import {
@@ -53,6 +56,9 @@ async function defaultOwnShellJobCount(sessionIds) {
53
56
 
54
57
  const lower = (value) => String(value || '').toLowerCase();
55
58
 
59
+ const WORKTREE_BOUND_SWITCH =
60
+ 'The session works in a managed worktree; exit the worktree first (switch to a project-folder branch, or merge or remove the worktree) before moving it to another project.';
61
+
56
62
  /** The registered Project (projects.json) at `path`, or null. */
57
63
  async function defaultRegisteredProject(path) {
58
64
  const { listProjectsStrict } = await import('../../runtime/shared/projects.mjs');
@@ -62,9 +68,12 @@ async function defaultRegisteredProject(path) {
62
68
  /**
63
69
  * Re-root one session's prompt at `cwd`: environment facts and instruction
64
70
  * files for every session, project memory for the Lead only (workers never
65
- * carried it).
71
+ * carried it). A session with a conversation keeps its system prompt (cache
72
+ * intact) and gets a tail notice instead (refreshSessionWorkspaceContext);
73
+ * `move` ({ from, changes }: the session left the project folder `from` for
74
+ * a worktree) adds the uncommitted-changes and path lines to that notice.
66
75
  */
67
- export async function refreshWorkspacePrompt(session, cwd, { loadCoreMemoryContext } = {}) {
76
+ export async function refreshWorkspacePrompt(session, cwd, { loadCoreMemoryContext, move = null } = {}) {
68
77
  const { environmentContext, instructionFilesContext } = buildWorkspacePromptContext(session, cwd);
69
78
  const coreMemoryContext =
70
79
  session?.owner === AGENT_OWNER || typeof loadCoreMemoryContext !== 'function'
@@ -75,6 +84,7 @@ export async function refreshWorkspacePrompt(session, cwd, { loadCoreMemoryConte
75
84
  environmentContext,
76
85
  coreMemoryContext,
77
86
  instructionFilesContext,
87
+ ...(move ? { move } : {}),
78
88
  });
79
89
  }
80
90
 
@@ -137,9 +147,10 @@ const TRANSITION_STATE = { worktree: 'provisioning', branch: 'switching', 'remov
137
147
  * @param {object} deps
138
148
  * @param {() => any} deps.getSession The Lead session this runtime hosts.
139
149
  * @param {() => string} deps.getCurrentCwd
140
- * @param {(cwd: string) => unknown} deps.applyResolvedCwd Retargets rt cwd, session.cwd and the cwd override.
150
+ * @param {(cwd: string, options?: { persistProjectSelection?: boolean }) => unknown} deps.applyResolvedCwd Retargets rt cwd, session.cwd and the cwd override (and, persisting, the Project selection).
151
+ * @param {(desktopSession: any) => unknown} [deps.setDesktopSession] Restores the runtime's desktop metadata on a rollback.
141
152
  * @param {(session: any) => unknown} deps.persistSession
142
- * @param {(session: any, cwd: string) => Promise<unknown>} deps.refreshWorkspaceContext
153
+ * @param {(session: any, cwd: string, move?: { from: string, changes: 'carried'|'left'|null }|null) => Promise<unknown>} deps.refreshWorkspaceContext
143
154
  * @param {() => number} deps.getActiveTurnCount
144
155
  * @param {() => { agentWorkers?: any[], agentJobs?: any[] }} deps.agentStatusState
145
156
  * @param {(sessionId: string, cwd: string) => unknown} [deps.updateLeadWorkerCwd] Moves the Lead's pool row (lead-workers.json).
@@ -149,6 +160,7 @@ export function createWorkspaceTransitions({
149
160
  getReservedSessionId = () => null,
150
161
  getCurrentCwd,
151
162
  getDesktopSession = () => null,
163
+ setDesktopSession = () => {},
152
164
  getConfig = () => null,
153
165
  createCurrentSession = null,
154
166
  applyResolvedCwd,
@@ -207,6 +219,37 @@ export function createWorkspaceTransitions({
207
219
  return normalizeSessionWorkspace({ projectPath, cwd: getCurrentCwd() || projectPath, revision: 0 });
208
220
  }
209
221
 
222
+ let liveWorkspace = null;
223
+
224
+ /**
225
+ * The workspace the session publishes: a worktree-bound record as stored; a
226
+ * project-folder session (never moved, back from a worktree, or carrying a
227
+ * transition stub) with the folder's checked-out branch / detached commit and
228
+ * repoCommonDir read straight from HEAD — no git process. Null outside a git
229
+ * repository for a session without a record. Identity-stable while equal.
230
+ */
231
+ function getLiveWorkspace() {
232
+ const session = getSession();
233
+ const stored = readSessionWorkspace(session);
234
+ if (isWorktreeBound(stored)) return session.workspace;
235
+ let base;
236
+ try {
237
+ base = stored ?? currentWorkspace(session);
238
+ } catch {
239
+ return session?.workspace ?? null;
240
+ }
241
+ const head = readGitHead(base.cwd);
242
+ if (!head) return session?.workspace ?? null;
243
+ const next = {
244
+ ...base,
245
+ repoCommonDir: head.repoCommonDir,
246
+ branch: head.branch,
247
+ baseCommit: head.branch ? base.baseCommit : head.commit,
248
+ };
249
+ if (!liveWorkspace || !isDeepStrictEqual(liveWorkspace, next)) liveWorkspace = next;
250
+ return liveWorkspace;
251
+ }
252
+
210
253
  function isSessionActive(session) {
211
254
  const runtime = sessionManager?.getSessionRuntime?.(session.id);
212
255
  if (runtime?.controller?.signal && !runtime.controller.signal.aborted) return true;
@@ -223,8 +266,11 @@ export function createWorkspaceTransitions({
223
266
  (candidate.ownerSessionId === session.id || candidate.parentSessionId === session.id)
224
267
  );
225
268
 
226
- /** WORKTREE_BUSY_JOBS while this Lead has a turn, workers or background shells running. */
227
- async function assertLeadIdle(session, { ownTurn = false } = {}) {
269
+ /**
270
+ * WORKTREE_BUSY_JOBS while this Lead has a turn, workers or background
271
+ * shells running (`shellJobs: false` tolerates background shells).
272
+ */
273
+ async function assertLeadIdle(session, { ownTurn = false, shellJobs: checkShellJobs = true } = {}) {
228
274
  if (!ownTurn && getActiveTurnCount() > 0) {
229
275
  throw worktreeError('WORKTREE_BUSY_JOBS', 'The session is running a turn; wait for it to finish.', {
230
276
  reason: 'turn',
@@ -240,6 +286,7 @@ export function createWorkspaceTransitions({
240
286
  if (busyWorkers || busyJobs) {
241
287
  throw worktreeError('WORKTREE_BUSY_JOBS', undefined, { reason: 'workers', workers: busyWorkers, jobs: busyJobs });
242
288
  }
289
+ if (!checkShellJobs) return;
243
290
  const ids = new Set([session.id, ...workers.map((row) => row?.sessionId).filter(Boolean)]);
244
291
  for (const worker of workersOf(session)) ids.add(worker.id);
245
292
  const shellJobs = await countOwnShellJobs([...ids]);
@@ -284,11 +331,44 @@ export function createWorkspaceTransitions({
284
331
  }
285
332
  }
286
333
 
334
+ /**
335
+ * Before a carry clears the shared checkout `folder` (held by the caller,
336
+ * so no new turn starts there): WORKTREE_BUSY_JOBS while any other session
337
+ * working in it — idle or not, this Lead's workers included — owns a
338
+ * running background shell (holdProjectFolder already refused running
339
+ * turns and agents there, assertLeadIdle this Lead's own). Processes
340
+ * outside Mixdog (editors, watchers) cannot be excluded.
341
+ */
342
+ async function assertCheckoutQuiet(session, folder) {
343
+ const sharing = listLiveSessions()
344
+ .filter(
345
+ (candidate) =>
346
+ candidate?.id &&
347
+ candidate.id !== session.id &&
348
+ candidate.closed !== true &&
349
+ typeof candidate.cwd === 'string' &&
350
+ isWithin(folder, candidate.cwd)
351
+ )
352
+ .map((candidate) => candidate.id);
353
+ if (!sharing.length) return;
354
+ const shellJobs = await countOwnShellJobs(sharing);
355
+ if (shellJobs > 0) {
356
+ throw worktreeError(
357
+ 'WORKTREE_BUSY_JOBS',
358
+ 'Background shells of other sessions are running in the project folder; carrying the changes needs it quiet.',
359
+ { reason: 'folder-shell-jobs', shellJobs }
360
+ );
361
+ }
362
+ }
363
+
287
364
  const steps = createTransitionSteps({
365
+ assertCheckoutQuiet,
288
366
  backend,
289
367
  inspectProject,
290
368
  runtimeToken,
291
369
  getCurrentCwd,
370
+ getDesktopSession,
371
+ setDesktopSession,
292
372
  applyResolvedCwd,
293
373
  persistSession,
294
374
  refreshWorkspaceContext,
@@ -331,6 +411,7 @@ export function createWorkspaceTransitions({
331
411
  name: target.name,
332
412
  nameHint: options.nameHint,
333
413
  confirmLarge: options.confirmLarge,
414
+ changes: options.changes,
334
415
  });
335
416
  }
336
417
  if (target.kind === 'branch') {
@@ -442,9 +523,22 @@ export function createWorkspaceTransitions({
442
523
  });
443
524
  // A session in the project folder (never moved, or back from a worktree)
444
525
  // is on whatever that folder has checked out now.
445
- const checkedOut = menu.occupancy.find((row) => row.kind === 'current')?.branch ?? null;
526
+ // Its repository is the project's (a never-moved session has no stored
527
+ // record), and a detached checkout shows branch null with the commit in
528
+ // baseCommit — never mistaken for "not a repository".
529
+ let view = workspace;
530
+ if (!isWorktreeBound(workspace)) {
531
+ const project = await inspectProject(workspace.projectPath);
532
+ const checkedOut = project.currentBranch ?? null;
533
+ view = {
534
+ ...workspace,
535
+ repoCommonDir: project.repoCommonDir ?? workspace.repoCommonDir,
536
+ branch: checkedOut,
537
+ baseCommit: checkedOut ? workspace.baseCommit : (project.headCommit ?? null),
538
+ };
539
+ }
446
540
  return {
447
- workspace: isWorktreeBound(workspace) || !checkedOut ? workspace : { ...workspace, branch: checkedOut },
541
+ workspace: view,
448
542
  pendingWorktree: pendingIntent ? { name: pendingIntent.name } : null,
449
543
  branches: branchMenuEntries(menu.branches, menu.occupancy),
450
544
  occupancy: menu.occupancy,
@@ -510,17 +604,28 @@ export function createWorkspaceTransitions({
510
604
  * reuse it. Typed failures (large repository, disk, busy) therefore reach
511
605
  * the sender while its draft is still unsent, and the turn that follows
512
606
  * finds the session already bound.
607
+ *
608
+ * A provisioning that creates a new worktree decides the project folder's
609
+ * uncommitted changes (tracked and untracked): none → it proceeds; without
610
+ * `carryChanges` it refuses with WORKTREE_DIRTY_CHOICE (`details.count`);
611
+ * `leave` starts the worktree at HEAD and leaves them; `carry` moves them
612
+ * into it (the result's `changes` reports which). Re-entering a retained
613
+ * worktree never touches them.
513
614
  */
514
615
  async function requestWorktree({
515
616
  name = null,
516
617
  confirmLarge = false,
517
618
  provision = false,
619
+ carryChanges,
518
620
  prompt = '',
519
621
  txId = '',
520
622
  expectedRevision,
521
623
  } = {}) {
522
624
  const nameHint = validName(name);
523
625
  const expected = validRevision(expectedRevision);
626
+ if (carryChanges !== undefined && carryChanges !== null && !WORKTREE_CARRY_CHOICES.includes(carryChanges)) {
627
+ throw new TypeError(`carryChanges must be one of ${WORKTREE_CARRY_CHOICES.join(', ')}.`);
628
+ }
524
629
  const session = provision === true ? await ensureSession() : getSession();
525
630
  // No session yet: the menu it was read from had revision 0.
526
631
  const current = session?.id ? currentWorkspace(session) : null;
@@ -536,6 +641,7 @@ export function createWorkspaceTransitions({
536
641
  return { ...entered, pending: false };
537
642
  }
538
643
  }
644
+ const priorIntent = pendingIntent;
539
645
  pendingIntent = {
540
646
  name: nameHint ?? pendingIntent?.name ?? null,
541
647
  confirmLarge: confirmLarge === true || pendingIntent?.confirmLarge === true,
@@ -544,10 +650,21 @@ export function createWorkspaceTransitions({
544
650
  txId: pendingIntent?.txId ?? null,
545
651
  };
546
652
  if (provision === true) {
547
- return {
548
- ...(await provisionIntent(session, pendingIntent, txId, prompt, { expectedRevision: expected })),
549
- pending: false,
550
- };
653
+ const intent = pendingIntent;
654
+ try {
655
+ return {
656
+ ...(await provisionIntent(session, intent, txId, prompt, {
657
+ expectedRevision: expected,
658
+ changes: carryChanges ?? 'ask',
659
+ })),
660
+ pending: false,
661
+ };
662
+ } catch (error) {
663
+ // A refused move (uncommitted changes to decide, busy, …) is never
664
+ // re-armed for the next turn, where nobody could answer it.
665
+ if (pendingIntent === intent) pendingIntent = priorIntent;
666
+ throw error;
667
+ }
551
668
  }
552
669
  const workspace = session?.id ? currentWorkspace(session) : null;
553
670
  return { revision: workspace?.revision ?? 0, workspace, pending: true };
@@ -570,15 +687,35 @@ export function createWorkspaceTransitions({
570
687
  if (cleanup !== true || merged.merged !== true) return merged;
571
688
  // "Merge and clean up": with autoCleanup on, the merged worktree goes now
572
689
  // (a dirty one stays, reported); off, nothing is removed automatically.
690
+ // The merge is committed: a failed or refused cleanup (dirty, retained
691
+ // file recovery data, ...) is a cleanup notice, never a merge failure.
573
692
  if (worktreeSettings().autoCleanup !== true) {
574
- return { ...merged, cleanup: { removed: false, reason: 'auto-cleanup-off' } };
693
+ return {
694
+ ...merged,
695
+ cleanup: { removed: false, reason: 'auto-cleanup-off', message: 'Auto cleanup is off; the worktree is kept.' },
696
+ };
575
697
  }
576
698
  try {
577
699
  const removed = await removeWorktree({ force: false }, { ownTurn });
578
700
  return { ...merged, cleanup: removed.removed, revision: removed.revision, workspace: removed.workspace };
579
701
  } catch (error) {
580
- if (!isWorktreeError(error, 'WORKTREE_DIRTY') && !isWorktreeError(error, 'WORKTREE_UNMERGED')) throw error;
581
- return { ...merged, cleanup: { removed: false, reason: error.code } };
702
+ // The merge stands: a refused or incomplete cleanup (dirty, unmerged,
703
+ // retained file recovery data, partly removed, …) is reported, never
704
+ // thrown, with the workspace as it is now (an incomplete removal already
705
+ // returned to the project folder).
706
+ const workspace = currentWorkspace(session);
707
+ const code = typeof error?.code === 'string' ? error.code : '';
708
+ return {
709
+ ...merged,
710
+ cleanup: {
711
+ removed: false,
712
+ reason: code || 'WORKTREE_CLEANUP_FAILED',
713
+ ...(code ? { code } : {}),
714
+ message: String(error?.message || error),
715
+ },
716
+ revision: workspace.revision,
717
+ workspace,
718
+ };
582
719
  }
583
720
  }
584
721
 
@@ -620,6 +757,41 @@ export function createWorkspaceTransitions({
620
757
  }
621
758
  }
622
759
 
760
+ /**
761
+ * Move the hosted Lead to another Project folder (session set-project, the
762
+ * desktop/remote setCwd): the one authority for a Project switch, admitted
763
+ * like a workspace transition. Refused while a turn runs (`ownTurn`: the
764
+ * Lead's own tool call tolerates its calling turn), while workers run, and
765
+ * while the session works in a managed worktree. Serialized with turns and
766
+ * other workspace changes; the prompt refresh of the Lead and its idle
767
+ * workers completes (or rolls the switch back) before it resolves. Without
768
+ * a session (a draft) only the runtime cwd and the selection move.
769
+ * `classifyByMembership` (the desktop/remote move): a folder outside every
770
+ * registered Project is classified as a task (cwd-apply).
771
+ * @returns {Promise<string>} the runtime cwd after the switch
772
+ */
773
+ async function switchProject(cwd, { ownTurn = false, classifyByMembership = false } = {}) {
774
+ const selection = { classifyByMembership: classifyByMembership === true };
775
+ const session = getSession();
776
+ if (!session?.id) {
777
+ applyResolvedCwd(cwd, { persistProjectSelection: true, ...selection });
778
+ return getCurrentCwd();
779
+ }
780
+ const leave = beginWorkspaceTransition(session.id, `project:${newTxId()}`);
781
+ const journal = [];
782
+ try {
783
+ await assertLeadIdle(session, { ownTurn: ownTurn === true, shellJobs: false });
784
+ if (isWorktreeBound(readSessionWorkspace(session))) throw new Error(WORKTREE_BOUND_SWITCH);
785
+ await steps.switchProject(session, cwd, journal, selection);
786
+ return getCurrentCwd();
787
+ } catch (error) {
788
+ await undoAll(journal, error);
789
+ throw error;
790
+ } finally {
791
+ leave();
792
+ }
793
+ }
794
+
623
795
  async function removeWorktree({ force = false, expectedRevision } = {}, { ownTurn = false } = {}) {
624
796
  const expected = validRevision(expectedRevision);
625
797
  const session = hostedSession(null);
@@ -636,6 +808,8 @@ export function createWorkspaceTransitions({
636
808
  admitTurnWorkspace,
637
809
  /** The durable workspace record of the hosted session (null: project folder, never moved). */
638
810
  getWorkspace: () => getSession()?.workspace ?? null,
811
+ /** What the session snapshot publishes (see getLiveWorkspace). */
812
+ getLiveWorkspace,
639
813
  getBranchMenu,
640
814
  getProjectBranchMenu: projectBranches.getProjectBranchMenu,
641
815
  selectBranch,
@@ -644,6 +818,7 @@ export function createWorkspaceTransitions({
644
818
  requestWorktree,
645
819
  mergeWorkspace,
646
820
  removeWorktree,
821
+ switchProject,
647
822
  listWorktrees,
648
823
  pruneWorktrees,
649
824
  cleanupDeletedSession,
@@ -68,5 +68,13 @@ export function createCanonicalAgentControl({ getSessionService, getSessionRunti
68
68
  return await tool.execute(args, scopedContext);
69
69
  }
70
70
 
71
- return { canonicalAgentTool, execute };
71
+ /** Graceful shutdown: drain and release the canonical agent's index writers.
72
+ * Never builds the tool just to dispose it. */
73
+ async function dispose() {
74
+ if (!canonicalAgentToolPromise) return;
75
+ const tool = await canonicalAgentToolPromise.catch(() => null);
76
+ await tool?.disposeAsync?.({ settle: true });
77
+ }
78
+
79
+ return { canonicalAgentTool, execute, dispose };
72
80
  }
@@ -22,9 +22,22 @@ export function createLocalSessionBridge({ getSessionService, log, pid = process
22
22
  return createSessionProtocolClient({
23
23
  call(name, args = {}, options = {}) {
24
24
  if (closed) throw new Error('daemon-local session client is closed');
25
- return getSessionService().handleCall(name, args, {
25
+ const signal = options?.signal;
26
+ signal?.throwIfAborted();
27
+ const result = getSessionService().handleCall(name, args, {
26
28
  clientToken,
27
29
  ...(options?.callId ? { callId: String(options.callId) } : {}),
30
+ ...(signal ? { signal } : {}),
31
+ });
32
+ if (!signal) return result;
33
+ // The in-process service has no cancellation of its own: an abort
34
+ // frees the caller, whose late reply is dropped.
35
+ return new Promise((resolve, reject) => {
36
+ const onAbort = () => reject(signal.reason);
37
+ signal.addEventListener('abort', onAbort, { once: true });
38
+ Promise.resolve(result)
39
+ .then(resolve, reject)
40
+ .finally(() => signal.removeEventListener('abort', onAbort));
28
41
  });
29
42
  },
30
43
  async close(reason = 'local session view closed') {