mixdog 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  4. package/scripts/gate-local.mjs +183 -63
  5. package/scripts/latest-guard.mjs +21 -9
  6. package/scripts/lib/gate-scheduler.mjs +138 -32
  7. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +91 -66
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +124 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/lib/test-failure-records.mjs +14 -1
  14. package/scripts/lib/test-failure-summary.mjs +16 -4
  15. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  16. package/scripts/post-release-check.mjs +1 -6
  17. package/scripts/pptx-kit-index.mjs +17 -5
  18. package/scripts/suite-health.mjs +115 -24
  19. package/scripts/test-data-dir.mjs +10 -7
  20. package/scripts/test.mjs +9 -14
  21. package/src/agents/advisor/AGENT.md +4 -4
  22. package/src/agents/advisor/agent.json +1 -1
  23. package/src/agents/explore/AGENT.md +22 -0
  24. package/src/agents/explore/agent.json +6 -0
  25. package/src/app.mjs +4 -0
  26. package/src/cli.mjs +7 -0
  27. package/src/defaults/agents.json +7 -0
  28. package/src/defaults/shipped-history.json +23 -2
  29. package/src/defaults/skills/browser-use/SKILL.md +10 -5
  30. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  31. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  32. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  33. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  34. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  35. package/src/defaults/skills/computer-use/SKILL.md +16 -4
  36. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  37. package/src/defaults/skills/setup/references/actions.md +12 -6
  38. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  39. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  40. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  41. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  42. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  43. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  44. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  45. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  46. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  47. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  48. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  49. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  50. package/src/defaults/skills/visualize/SKILL.md +6 -2
  51. package/src/lib/rules-builder.cjs +35 -42
  52. package/src/output-styles/common.md +2 -2
  53. package/src/output-styles/simple.md +1 -1
  54. package/src/rules/agent/AGENT.md +5 -4
  55. package/src/rules/lead/LEAD.md +12 -7
  56. package/src/rules/shared/10-tool-workflow.md +3 -1
  57. package/src/rules/shared/20-research.md +3 -6
  58. package/src/rules/shared/40-editing.md +1 -0
  59. package/src/rules/shared/45-destructive.md +0 -2
  60. package/src/rules/shared/50-execution.md +2 -0
  61. package/src/rules/shared/60-verification.md +10 -9
  62. package/src/rules/shared/70-delivery.md +8 -9
  63. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  64. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
  66. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  67. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  69. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
  71. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  72. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  74. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  75. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  77. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  78. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  79. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
  80. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  81. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
  82. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
  84. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  85. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  86. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  89. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  90. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  91. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  92. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  94. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  97. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  100. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  101. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  103. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  105. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  106. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
  108. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  109. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  110. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  113. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  114. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  115. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  117. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  129. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  130. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  131. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  132. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  133. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
  135. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  136. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
  137. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  139. package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
  140. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  141. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  142. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  143. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  145. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
  147. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  148. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
  150. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  151. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  152. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  153. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  154. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  155. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  156. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
  157. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  158. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  159. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  160. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  161. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  162. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  163. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
  164. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
  165. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  166. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  167. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
  168. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  169. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  170. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  171. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
  172. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  174. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  175. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  179. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  180. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  181. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  184. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  185. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  186. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  187. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
  188. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
  189. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  190. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
  192. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
  193. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  194. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  195. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
  196. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  197. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  198. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
  199. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  200. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  201. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  202. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  203. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  204. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
  209. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  210. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  211. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  212. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  213. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  214. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  215. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  217. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  218. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  219. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
  221. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  223. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  224. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  225. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  226. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  227. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  228. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  229. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  231. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  232. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  233. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  234. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  235. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  236. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  237. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  238. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  239. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  240. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  241. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  243. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  244. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
  245. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  246. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  248. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  249. package/src/runtime/channels/lib/whisper-port.mjs +29 -11
  250. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  251. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  252. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  253. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  254. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  255. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  256. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  257. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  258. package/src/runtime/explore/explorer.mjs +93 -0
  259. package/src/runtime/explore/tool-defs.mjs +35 -0
  260. package/src/runtime/explore/tool.mjs +123 -0
  261. package/src/runtime/local-provider/server-process.mjs +2 -1
  262. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  263. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  264. package/src/runtime/memory/index.mjs +4 -0
  265. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  266. package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
  267. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  268. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  269. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  270. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  271. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  272. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  273. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  274. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  275. package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
  276. package/src/runtime/office/quality/design-review.mjs +1 -5
  277. package/src/runtime/office/quality/document-brief.mjs +1 -5
  278. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  279. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  280. package/src/runtime/shared/agent-route-config.mjs +17 -2
  281. package/src/runtime/shared/atomic-file.mjs +103 -2
  282. package/src/runtime/shared/background-tasks.mjs +15 -6
  283. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  284. package/src/runtime/shared/connector-accounts.mjs +5 -5
  285. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  286. package/src/runtime/shared/connector-catalog.mjs +60 -52
  287. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  288. package/src/runtime/shared/defaults-separation.mjs +1 -4
  289. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  290. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  291. package/src/runtime/shared/file-has-data.mjs +26 -2
  292. package/src/runtime/shared/file-lock.mjs +13 -15
  293. package/src/runtime/shared/git-launch.d.mts +9 -0
  294. package/src/runtime/shared/git-launch.mjs +213 -0
  295. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  296. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  297. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  298. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  299. package/src/runtime/shared/llm/cost.mjs +11 -2
  300. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  301. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  302. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  303. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  304. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  305. package/src/runtime/shared/loopback-listener.mjs +25 -2
  306. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  307. package/src/runtime/shared/model-route-chain.mjs +22 -4
  308. package/src/runtime/shared/open-url.mjs +10 -0
  309. package/src/runtime/shared/provider-accounts.mjs +52 -2
  310. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  311. package/src/runtime/shared/session-scratch.mjs +37 -0
  312. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  313. package/src/runtime/shared/staged-update.mjs +47 -2
  314. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  315. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  316. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  317. package/src/runtime/shared/tool-surface.mjs +113 -36
  318. package/src/runtime/shared/tool-work-units.mjs +6 -0
  319. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  320. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  321. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  322. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  323. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  324. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  325. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  326. package/src/runtime/shared/worktrees/git.mjs +72 -0
  327. package/src/runtime/shared/worktrees/manager.mjs +285 -168
  328. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  329. package/src/runtime/tidy/install.mjs +95 -15
  330. package/src/runtime/tidy/languages.mjs +17 -15
  331. package/src/runtime/tidy/process.mjs +7 -4
  332. package/src/runtime/tidy/resolve.mjs +19 -16
  333. package/src/runtime/tidy/runners/biome.mjs +3 -3
  334. package/src/runtime/tidy/tool.mjs +1 -1
  335. package/src/runtime/tidy/worktree.mjs +10 -6
  336. package/src/runtime/visualize-bridge/client.mjs +6 -1
  337. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  338. package/src/runtime/web-search/index.mjs +1 -1
  339. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  340. package/src/session-runtime/agent-task-sessions.mjs +458 -312
  341. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  342. package/src/session-runtime/boot/tools.mjs +7 -1
  343. package/src/session-runtime/bot-status.mjs +107 -101
  344. package/src/session-runtime/bot-target.mjs +38 -0
  345. package/src/session-runtime/channel-config-api.mjs +6 -1
  346. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  347. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  348. package/src/session-runtime/conversation-import/discovery.mjs +401 -36
  349. package/src/session-runtime/conversation-import/service.mjs +35 -8
  350. package/src/session-runtime/handoff-attachments.mjs +82 -31
  351. package/src/session-runtime/held-requests.mjs +3 -1
  352. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  353. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  354. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  355. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  356. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  357. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
  358. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  359. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  360. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  361. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  362. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  363. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  364. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  365. package/src/session-runtime/model-route/set-route.mjs +11 -2
  366. package/src/session-runtime/provider-auth/login.mjs +2 -2
  367. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  368. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  369. package/src/session-runtime/provider-models.mjs +4 -2
  370. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  371. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  372. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  373. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  374. package/src/session-runtime/resource-recall-api.mjs +6 -1
  375. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  376. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  377. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  378. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  379. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  380. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  381. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  382. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  383. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  384. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  385. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  386. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  387. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  388. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  389. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  390. package/src/session-runtime/services/provider-admin.mjs +283 -11
  391. package/src/session-runtime/session-turn-api.mjs +2 -0
  392. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  393. package/src/session-runtime/settings-system-api.mjs +1 -1
  394. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  395. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  396. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  397. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  398. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  399. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  400. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  401. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  402. package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
  403. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  404. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  405. package/src/session-runtime/workspace/session-view.mjs +149 -0
  406. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  407. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  408. package/src/session-runtime/workspace/transitions.mjs +108 -1041
  409. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  410. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  411. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  412. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  413. package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
  414. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  415. package/src/standalone/daemon-session-control.mjs +169 -138
  416. package/src/standalone/daemon.mjs +7 -0
  417. package/src/standalone/desktop-service-registry.mjs +9 -0
  418. package/src/standalone/local-session-runtime.mjs +5 -1
  419. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  420. package/src/standalone/session-client.mjs +5 -1
  421. package/src/standalone/session-frame-stream.mjs +83 -11
  422. package/src/standalone/session-protocol.mjs +4 -0
  423. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  424. package/src/standalone/session-service/agent-tree.mjs +45 -9
  425. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  426. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  427. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  428. package/src/standalone/session-service/stored-reader.mjs +127 -40
  429. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  430. package/src/tui/App.jsx +154 -164
  431. package/src/tui/app/app-view/floating-panel.jsx +10 -4
  432. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  433. package/src/tui/app/clipboard.mjs +21 -10
  434. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  435. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  436. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  437. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  438. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  439. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  440. package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
  441. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  442. package/src/tui/app/provider-setup-picker.mjs +29 -18
  443. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  444. package/src/tui/app/slash-commands.mjs +1 -0
  445. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  446. package/src/tui/app/slash-dispatch.mjs +1 -1
  447. package/src/tui/app/use-btw-panel.mjs +29 -0
  448. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  449. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  450. package/src/tui/app/use-mouse-input.mjs +11 -0
  451. package/src/tui/app/use-onboarding-start.mjs +8 -3
  452. package/src/tui/app/use-panel-transition.mjs +10 -1
  453. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  454. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  455. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  456. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  457. package/src/tui/app/use-selection-focus.mjs +11 -2
  458. package/src/tui/app/use-selection-geometry.mjs +18 -9
  459. package/src/tui/app/use-selection-paint.mjs +4 -4
  460. package/src/tui/app/use-selection-stitch.mjs +3 -3
  461. package/src/tui/app/use-slash-palette.mjs +62 -0
  462. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  463. package/src/tui/app/use-transcript-activity.mjs +47 -32
  464. package/src/tui/app/use-transcript-follow.mjs +12 -4
  465. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  466. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  467. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  468. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  469. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  470. package/src/tui/app/use-transcript-window.mjs +21 -11
  471. package/src/tui/app/use-tui-ready.mjs +18 -0
  472. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  473. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  474. package/src/tui/components/Picker.jsx +27 -3
  475. package/src/tui/components/QuestionPanel.jsx +13 -4
  476. package/src/tui/components/SignInUrl.jsx +68 -0
  477. package/src/tui/components/TextEntryPanel.jsx +76 -46
  478. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  479. package/src/tui/dist/index.mjs +1963 -1361
  480. package/src/tui/session/goal-turn-state.mjs +6 -2
  481. package/src/tui/session/session-api/controls.mjs +4 -0
  482. package/src/tui/session/session-api/extensions.mjs +8 -0
  483. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  484. package/src/tui/session/session-api/intake.mjs +2 -0
  485. package/src/tui/session/session-api/integrations.mjs +2 -2
  486. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  487. package/src/tui/session/session-api/settings.mjs +2 -2
  488. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  489. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  490. package/src/tui/session/transcript-spill.mjs +92 -4
  491. package/src/tui/session/transcript-store.mjs +2 -0
  492. package/src/ui/statusline-agents.mjs +1 -3
  493. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -0,0 +1,56 @@
1
+ // The admission thread (activity-gate.mjs): runs the ledger operations its
2
+ // starter posts, one at a time and in the order they were posted. A request it
3
+ // answers is complete — both copies written, or refused — and nothing here
4
+ // ever waits for the thread that posted it.
5
+ import { parentPort, workerData } from 'node:worker_threads';
6
+ import { LEDGER_SLOTS as SLOT, admit, hold, release } from './activity-ledger.mjs';
7
+
8
+ const slots = new Int32Array(workerData.slots);
9
+ const { syncPort } = workerData;
10
+ const operations = { admit, hold, release };
11
+
12
+ // Fault injection for tests only (MIXDOG_ADMISSION_TEST_FAULT_HOOK=1): the
13
+ // one fixed test-support module beside this file, loaded before the first
14
+ // request is taken. It is not shipped (package.json `files`), so in an
15
+ // installed runtime the flag does nothing.
16
+ if (workerData.faultHook) {
17
+ try {
18
+ await import(new URL('./activity-gate-faults.test-hook.mjs', import.meta.url).href);
19
+ } catch (error) {
20
+ if (error?.code !== 'ERR_MODULE_NOT_FOUND') throw error;
21
+ }
22
+ }
23
+
24
+ process.on('exit', () => {
25
+ Atomics.store(slots, SLOT.STOPPED, 1);
26
+ Atomics.notify(slots, SLOT.ANSWERED);
27
+ Atomics.notify(slots, SLOT.DONE);
28
+ });
29
+
30
+ const serialize = (error) => ({
31
+ name: error?.name,
32
+ message: String(error?.message ?? error),
33
+ code: error?.code,
34
+ details: error?.details,
35
+ });
36
+
37
+ parentPort.on('message', ({ id, op, args, sync }) => {
38
+ let answer;
39
+ try {
40
+ answer = { id, value: operations[op](args) };
41
+ } catch (error) {
42
+ answer = { id, error: serialize(error) };
43
+ }
44
+ if (!sync) parentPort.postMessage(answer);
45
+ else if (Atomics.compareExchange(slots, SLOT.SYNC, id, -id) === id) {
46
+ syncPort.postMessage(answer);
47
+ Atomics.add(slots, SLOT.ANSWERED, 1);
48
+ Atomics.notify(slots, SLOT.ANSWERED);
49
+ } else if (op !== 'release' && answer.value) {
50
+ // Its caller stopped waiting and reported the outcome unknown: nobody
51
+ // holds this grant, so it is withdrawn here.
52
+ release(answer.value);
53
+ }
54
+ Atomics.add(slots, SLOT.DONE, 1);
55
+ Atomics.notify(slots, SLOT.DONE);
56
+ });
@@ -0,0 +1,391 @@
1
+ /*
2
+ * The workspace admission ledger. Only the admission thread runs the ledger
3
+ * operations below (activity-ledger-worker.mjs, started by activity-gate.mjs):
4
+ * it owns this module's bookkeeping (`held`, `pending`, the heal timer) and
5
+ * runs one operation at a time, in the order requested. Every other thread
6
+ * of the process asks it through activity-gate.mjs.
7
+ */
8
+ import { randomUUID } from 'node:crypto';
9
+ import { readFileSync } from 'node:fs';
10
+ import { isAbsolute, join } from 'node:path';
11
+ import { parseJsonText, quarantinePath, withFileLockSync, writeFileAtomicSync } from '../atomic-file.mjs';
12
+ import { isPidAliveOrUnknown } from '../pid-liveness.mjs';
13
+ import { findGitLayout } from './git-head.mjs';
14
+ import { canonicalPath, isWithin, samePath } from './paths.mjs';
15
+ import { worktreeError, worktreeRefusal } from './contract.mjs';
16
+ import { readRegistrySync } from './registry.mjs';
17
+
18
+ const FILE = 'mixdog-workspace-admission.json';
19
+ const overlaps = (a, b) => isWithin(a, b) || isWithin(b, a);
20
+
21
+ /**
22
+ * The Int32 slots the admission thread shares with the thread that started
23
+ * it. SYNC: the id of the synchronous request its caller waits for; the
24
+ * admission thread swaps it to -id when it answers, the caller to 0 when it
25
+ * gives up — whichever comes first decides. ANSWERED counts synchronous
26
+ * answers, DONE every finished request, STOPPED is set once the admission
27
+ * thread is gone.
28
+ */
29
+ export const LEDGER_SLOTS = Object.freeze({ SYNC: 0, ANSWERED: 1, DONE: 2, STOPPED: 3, COUNT: 4 });
30
+
31
+ /**
32
+ * The admission policy's external exception: a linked checkout Mixdog did not
33
+ * create (not a project's main folder, not a registered managed worktree) is
34
+ * not lock-blocked by what is held elsewhere. Management takes no lease on it
35
+ * and its readers are admitted whatever is held outside it; its running
36
+ * readers still refuse a management action that touches it. A management lease
37
+ * published inside the checkout itself (another profile, or a registry
38
+ * recovered empty) still refuses its readers.
39
+ */
40
+ export function isExternalCheckout(folder) {
41
+ const layout = findGitLayout(folder, { strict: true });
42
+ if (!layout || samePath(layout.gitDir, layout.commonDir)) return false;
43
+ let entries;
44
+ try {
45
+ entries = readRegistrySync().entries;
46
+ } catch {
47
+ // A damaged registry is kept aside and reads as a restored or empty one;
48
+ // only a real I/O failure (access denied, in use) lands here.
49
+ throw worktreeRefusal('workspace-admission-unreadable');
50
+ }
51
+ return !Object.values(entries).some(
52
+ (record) => typeof record?.path === 'string' && samePath(record.path, layout.topLevel)
53
+ );
54
+ }
55
+
56
+ /*
57
+ * The admission ledger lists every turn, command and management lease of a
58
+ * repository. It is kept in two copies with the same content in the Git
59
+ * common directory: `mixdog-workspace-admission.json` and its `.management`
60
+ * mirror. A grant returns its release only once both copies hold it, and a
61
+ * release is forgotten only once both copies dropped it, so damage to either
62
+ * copy alone never loses an acknowledged lease. `recoveredAt` marks a ledger
63
+ * that may be missing leases because no complete copy survived: readers still
64
+ * run, management refuses, and only a quiescent reset that removes both
65
+ * copies clears it (docs/git-github-integration.md). Time, healing and
66
+ * releases never do.
67
+ */
68
+ const VERSION = 2;
69
+ const isLease = (lease) =>
70
+ !!lease &&
71
+ ['turn', 'command', 'management'].includes(lease.kind) &&
72
+ typeof lease.token === 'string' &&
73
+ Number.isSafeInteger(lease.pid) &&
74
+ lease.pid > 0 &&
75
+ typeof lease.path === 'string' &&
76
+ isAbsolute(lease.path);
77
+ // Only a pid PROVEN dead (ESRCH) drops its lease, whatever its kind; an
78
+ // unprobeable pid still counts as running. A live runtime keeps a command
79
+ // whose tree it cannot verify reserved under its own (live) pid until the
80
+ // tree is confirmed gone, so a lease that outlives every pid belongs to a
81
+ // restarted runtime that can no longer release it.
82
+ const aliveLease = (lease) => isPidAliveOrUnknown(lease.pid);
83
+ // A management hold shares one token across its folders, so a lease is
84
+ // identified by token and path.
85
+ const leaseKey = (lease) => `${lease.token}\0${lease.path}`;
86
+ const unverifiable = () => worktreeRefusal('workspace-admission-unreadable');
87
+ const copyPaths = (commonDir) => {
88
+ const primary = join(commonDir, FILE);
89
+ return [primary, `${primary}.management`];
90
+ };
91
+ export const envMs = (name, fallback) => {
92
+ const value = Number(process.env[name]);
93
+ return Number.isFinite(value) && value > 0 ? value : fallback;
94
+ };
95
+ // How often the held leases are checked against both copies: a retry period
96
+ // for repair, never a lease decision (liveness is the pid probe, aliveLease).
97
+ const healIntervalMs = () => envMs('MIXDOG_ADMISSION_HEAL_MS', 2_000);
98
+
99
+ // The leases this process holds, put back into a ledger that lost them, and
100
+ // the tokens it released or failed to grant whose removal from both copies is
101
+ // not confirmed yet (token -> common dir), retried until it is.
102
+ const held = new Map();
103
+ const pending = new Map();
104
+ let healTimer = null;
105
+
106
+ /**
107
+ * One copy as read under the ledger lock. A copy that cannot be read (access
108
+ * denied, in use, a directory) or that a newer build wrote refuses everything
109
+ * and is never written: neither may pass for an empty ledger. Anything else
110
+ * that is not a ledger, or holds an invalid lease record, is damaged; its
111
+ * valid records still count. A copy is complete when it accounts for every
112
+ * acknowledged lease: intact, not uncertain, and version 2 — or the version 1
113
+ * primary, since the version 1 mirror held management leases only.
114
+ */
115
+ function readCopy(path, primary) {
116
+ let bytes;
117
+ try {
118
+ bytes = readFileSync(path);
119
+ } catch (error) {
120
+ if (error?.code === 'ENOENT') return { path, leases: [] };
121
+ throw unverifiable();
122
+ }
123
+ let ledger = null;
124
+ try {
125
+ ledger = parseJsonText(bytes.toString('utf8'));
126
+ } catch {
127
+ // Unparseable: damaged below.
128
+ }
129
+ if (Number.isSafeInteger(ledger?.version) && ledger.version > VERSION) throw unverifiable();
130
+ if (![1, VERSION].includes(ledger?.version) || !Array.isArray(ledger.leases)) {
131
+ return { path, bytes, leases: [], damaged: true };
132
+ }
133
+ const leases = ledger.leases.filter(isLease);
134
+ const damaged = leases.length !== ledger.leases.length;
135
+ const uncertain = Object.hasOwn(ledger, 'recoveredAt');
136
+ const complete = !damaged && !uncertain && (ledger.version === VERSION || primary);
137
+ return { path, bytes, leases, damaged, uncertain, complete, recoveredAt: ledger.recoveredAt };
138
+ }
139
+
140
+ /**
141
+ * The ledger recovered from both copies: the union of their valid lease
142
+ * records (one copy lacking a lease never drops it), less proven-dead pids
143
+ * and this process's pending removals, plus the leases this process holds.
144
+ * It stays uncertain when either copy is, and becomes so when a copy exists
145
+ * but none is complete. Both copies missing reads as a new repository.
146
+ */
147
+ function recover(commonDir, copies) {
148
+ const own = [...held.values()].filter((entry) => entry.commonDir === commonDir).map((entry) => entry.lease);
149
+ const leases = new Map();
150
+ for (const lease of [...copies.flatMap((copy) => copy.leases), ...own]) {
151
+ const key = leaseKey(lease);
152
+ if (!leases.has(key) && pending.get(lease.token) !== commonDir && aliveLease(lease)) leases.set(key, lease);
153
+ }
154
+ const ledger = { version: VERSION, leases: [...leases.values()] };
155
+ const uncertain =
156
+ copies.some((copy) => copy.uncertain) ||
157
+ (copies.some((copy) => copy.bytes) && !copies.some((copy) => copy.complete));
158
+ if (uncertain) ledger.recoveredAt = copies.map((copy) => copy.recoveredAt).find(Number.isFinite) ?? Date.now();
159
+ return ledger;
160
+ }
161
+
162
+ /**
163
+ * #117: the bytes of a damaged copy are copied aside as `<copy>.corrupt-<ts>`
164
+ * before it is rewritten. If they cannot be kept, nothing is written and
165
+ * everything refuses.
166
+ */
167
+ function keepDamagedAside(copies) {
168
+ for (const copy of copies.filter((each) => each.damaged)) {
169
+ const aside = quarantinePath(copy.path);
170
+ try {
171
+ writeFileAtomicSync(aside, copy.bytes);
172
+ } catch {
173
+ throw unverifiable();
174
+ }
175
+ process.stderr.write(`[state] ${copy.path} is damaged; kept as ${aside}\n`);
176
+ }
177
+ }
178
+
179
+ /** Writes the primary, then the mirror; a copy that already holds the ledger is left alone. */
180
+ function publish(copies, ledger) {
181
+ const text = Buffer.from(`${JSON.stringify(ledger, null, 2)}\n`);
182
+ for (const copy of copies) {
183
+ if (copy.bytes?.equals(text)) continue;
184
+ copy.bytes = null; // Unknown until the write lands.
185
+ writeFileAtomicSync(copy.path, text);
186
+ copy.bytes = text;
187
+ }
188
+ }
189
+
190
+ function settle(tokens) {
191
+ for (const token of tokens) pending.delete(token);
192
+ syncHealTimer();
193
+ }
194
+
195
+ /**
196
+ * Runs `update(leases, { unknown })` on the recovered ledger under the
197
+ * primary copy's lock and publishes the result to both copies before
198
+ * returning. `unknown`: the ledger may be missing leases. A refused update
199
+ * still publishes the repaired ledger. A grant that cannot be published is
200
+ * refused: its leases become pending removals and the ledger without them is
201
+ * published again; whatever that leaves behind is withdrawn by the next
202
+ * mutation or heal tick, which also blocks every later grant until it lands.
203
+ */
204
+ function mutate(commonDir, update = () => {}) {
205
+ const paths = copyPaths(commonDir);
206
+ return withFileLockSync(
207
+ `${paths[0]}.lock`,
208
+ () => {
209
+ const copies = paths.map((path, index) => readCopy(path, index === 0));
210
+ keepDamagedAside(copies);
211
+ const withdrawn = [...pending].filter(([, dir]) => dir === commonDir).map(([token]) => token);
212
+ const base = recover(commonDir, copies);
213
+ const leases = [...base.leases];
214
+ let result;
215
+ let refusal = null;
216
+ try {
217
+ result = update(leases, { unknown: Object.hasOwn(base, 'recoveredAt') });
218
+ } catch (error) {
219
+ refusal = error;
220
+ }
221
+ const granted = refusal ? [] : leases.filter((lease) => !base.leases.includes(lease));
222
+ try {
223
+ publish(copies, refusal ? base : { ...base, leases });
224
+ settle(withdrawn);
225
+ } catch (error) {
226
+ process.stderr.write(`[state] ${paths[0]} could not be published (${error?.message})\n`);
227
+ if (refusal) throw refusal;
228
+ for (const lease of granted) pending.set(lease.token, commonDir);
229
+ if (granted.length) {
230
+ try {
231
+ publish(copies, base);
232
+ settle([...withdrawn, ...granted.map((lease) => lease.token)]);
233
+ } catch {
234
+ syncHealTimer();
235
+ }
236
+ }
237
+ throw unverifiable();
238
+ }
239
+ if (refusal) throw refusal;
240
+ return result;
241
+ },
242
+ { strictOwner: true }
243
+ );
244
+ }
245
+
246
+ // Without the lock, a cheap check that both copies are the same current
247
+ // ledger holding every live lease this process holds there and none of the
248
+ // tokens it is withdrawing. Repair, never proof that the ledger is complete.
249
+ function inSync(commonDir) {
250
+ let ledger;
251
+ try {
252
+ const [primary, mirror] = copyPaths(commonDir).map((path) => readFileSync(path));
253
+ if (!primary.equals(mirror)) return false;
254
+ ledger = parseJsonText(primary.toString('utf8'));
255
+ } catch {
256
+ return false;
257
+ }
258
+ if (ledger?.version !== VERSION || !Array.isArray(ledger.leases)) return false;
259
+ const stored = new Set(ledger.leases.filter(isLease).map(leaseKey));
260
+ return (
261
+ [...held].every(([key, entry]) => entry.commonDir !== commonDir || stored.has(key) || !aliveLease(entry.lease)) &&
262
+ !ledger.leases.some((lease) => pending.get(lease?.token) === commonDir)
263
+ );
264
+ }
265
+
266
+ function healHeld() {
267
+ const dirs = new Set([...[...held.values()].map((entry) => entry.commonDir), ...pending.values()]);
268
+ for (const commonDir of dirs) {
269
+ if (inSync(commonDir)) continue;
270
+ try {
271
+ mutate(commonDir);
272
+ } catch {
273
+ // Retried on the next tick.
274
+ }
275
+ }
276
+ syncHealTimer();
277
+ }
278
+
279
+ function syncHealTimer() {
280
+ if (held.size || pending.size) {
281
+ healTimer ??= setInterval(healHeld, healIntervalMs());
282
+ healTimer.unref?.();
283
+ } else if (healTimer) {
284
+ clearInterval(healTimer);
285
+ healTimer = null;
286
+ }
287
+ }
288
+
289
+ function track(commonDir, lease) {
290
+ held.set(leaseKey(lease), { commonDir, lease });
291
+ syncHealTimer();
292
+ }
293
+
294
+ // Forgotten as held first, so nothing puts the lease back; its token stays
295
+ // pending until both copies dropped it. Never throws: a removal that did not
296
+ // land is retried by the next mutation or heal tick.
297
+ function releaseLease({ commonDir, token }) {
298
+ for (const [key, entry] of held) if (entry.lease.token === token) held.delete(key);
299
+ pending.set(token, commonDir);
300
+ try {
301
+ mutate(commonDir);
302
+ } catch {
303
+ // Retried by the next mutation or heal tick.
304
+ }
305
+ syncHealTimer();
306
+ }
307
+
308
+ /**
309
+ * Releases the handles a grant returned, last granted first (one per
310
+ * repository the grant touched).
311
+ */
312
+ export function release({ leases }) {
313
+ for (const handle of [...leases].reverse()) releaseLease(handle);
314
+ }
315
+
316
+ /**
317
+ * Readers share a checkout freely. Null means a management writer currently
318
+ * owns it; a reader in an external checkout is admitted whatever is held
319
+ * outside it, but refused by a management lease inside it. Otherwise the
320
+ * handles to release (none outside a Git checkout). Throws
321
+ * `workspace-admission-unreadable` when the ledger cannot be read, was
322
+ * written by a newer build, or the grant could not be made durable in both
323
+ * copies: no work may run unrecorded.
324
+ */
325
+ export function admit({ sessionId, cwd, pid = process.pid, kind = 'turn' }) {
326
+ const layout = findGitLayout(cwd, { strict: true });
327
+ if (!layout) return { leases: [] };
328
+ const external = isExternalCheckout(cwd);
329
+ const path = canonicalPath(cwd);
330
+ const token = randomUUID();
331
+ const lease = { kind, sessionId, pid, token, path };
332
+ // External is this profile's registry view: management another profile (or
333
+ // a registry recovered empty) published inside this very checkout still holds.
334
+ const checkout = canonicalPath(layout.topLevel);
335
+ const holds = (other) => (external ? isWithin(checkout, other.path) : overlaps(other.path, path));
336
+ const admitted = mutate(layout.commonDir, (leases) => {
337
+ if (leases.some((other) => other.kind === 'management' && holds(other))) return false;
338
+ leases.push(lease);
339
+ return true;
340
+ });
341
+ if (!admitted) return null;
342
+ track(layout.commonDir, lease);
343
+ return { leases: [{ commonDir: layout.commonDir, token }] };
344
+ }
345
+
346
+ /**
347
+ * Writers refuse while any process's reader overlaps an affected folder, then
348
+ * exclude later readers until the returned handles are released. External
349
+ * checkouts are only checked, never held. `sessionId`: the acting session,
350
+ * whose own readers in this process do not count (its idleness is the
351
+ * caller's check).
352
+ */
353
+ export function hold({ folders, sessionId = null }) {
354
+ const groups = new Map();
355
+ for (const folder of folders) {
356
+ const layout = findGitLayout(folder, { strict: true });
357
+ if (!layout) throw worktreeError('WORKTREE_UNSUPPORTED_REPO');
358
+ const paths = groups.get(layout.commonDir) ?? [];
359
+ paths.push({ path: canonicalPath(folder), external: isExternalCheckout(folder) });
360
+ groups.set(layout.commonDir, paths);
361
+ }
362
+ const handles = [];
363
+ try {
364
+ for (const [commonDir, paths] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
365
+ const token = randomUUID();
366
+ const taken = [];
367
+ mutate(commonDir, (leases, { unknown }) => {
368
+ const conflicts = leases.filter(
369
+ (lease) =>
370
+ !(sessionId && lease.sessionId === sessionId && lease.pid === process.pid) &&
371
+ paths.some(({ path }) => overlaps(path, lease.path))
372
+ );
373
+ if (conflicts.length) throw worktreeRefusal('folder-in-use');
374
+ // The ledger may be missing a live foreign lease: refused until reset.
375
+ if (unknown) throw unverifiable();
376
+ for (const { path, external } of paths) {
377
+ if (external) continue;
378
+ const lease = { kind: 'management', pid: process.pid, token, path };
379
+ leases.push(lease);
380
+ taken.push(lease);
381
+ }
382
+ });
383
+ for (const lease of taken) track(commonDir, lease);
384
+ handles.push({ commonDir, token });
385
+ }
386
+ } catch (error) {
387
+ release({ leases: handles });
388
+ throw error;
389
+ }
390
+ return { leases: handles };
391
+ }
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { branchCommit, inspectRepo, listWorktrees, runGit, validBranchName } from './git.mjs';
2
+ import { branchCommit, branchCommitPair, inspectRepo, listWorktrees, runGit, validBranchName } from './git.mjs';
3
3
  import { worktreeError, worktreeRefusal } from './contract.mjs';
4
4
  import { samePath } from './paths.mjs';
5
5
  import { getRecord, putRecord, readRegistry } from './registry.mjs';
@@ -37,7 +37,9 @@ function assertMergeTarget(currentBranch, target) {
37
37
  async function inspect(projectPath, branch) {
38
38
  const repo = await inspectRepo(projectPath);
39
39
  if (!(await validBranchName(repo.projectPath, branch))) throw new TypeError('Invalid local branch name.');
40
- const commit = await branchCommit(repo.projectPath, branch);
40
+ // Both tips in one read when both exist; otherwise each on its own, as before.
41
+ const pair = repo.currentBranch ? await branchCommitPair(repo.projectPath, branch, repo.currentBranch) : null;
42
+ const commit = pair ? pair[0] : await branchCommit(repo.projectPath, branch);
41
43
  if (!commit) throw worktreeError('WORKTREE_MISSING', 'The local branch no longer exists.');
42
44
  const holder = (await listWorktrees(repo.projectPath)).find((row) => row.branch === branch && !row.prunable);
43
45
  const entry = holder ? await getWorktreeByPath(holder.path) : null;
@@ -46,7 +48,11 @@ async function inspect(projectPath, branch) {
46
48
  branch,
47
49
  commit,
48
50
  target: repo.currentBranch,
49
- targetCommit: repo.currentBranch ? await branchCommit(repo.projectPath, repo.currentBranch) : null,
51
+ targetCommit: pair
52
+ ? pair[1]
53
+ : repo.currentBranch
54
+ ? await branchCommit(repo.projectPath, repo.currentBranch)
55
+ : null,
50
56
  path: holder?.path ?? null,
51
57
  worktreeId: entry?.id ?? null,
52
58
  kind: holder ? (samePath(holder.path, repo.topLevel) ? 'project' : entry ? 'managed' : 'external') : 'free',
@@ -27,6 +27,7 @@ const WORKTREE_ENTRY_STATES = Object.freeze(['creating', 'ready', 'deleting', 'r
27
27
  export const WORKTREE_ERROR_CODES = Object.freeze({
28
28
  WORKTREE_BUSY_JOBS: 'WORKTREE_BUSY_JOBS',
29
29
  WORKTREE_BRANCH_OCCUPIED: 'WORKTREE_BRANCH_OCCUPIED',
30
+ WORKTREE_DEFAULT_BRANCH: 'WORKTREE_DEFAULT_BRANCH',
30
31
  WORKTREE_DISK_INSUFFICIENT: 'WORKTREE_DISK_INSUFFICIENT',
31
32
  WORKTREE_LARGE_REPO_CONFIRM: 'WORKTREE_LARGE_REPO_CONFIRM',
32
33
  WORKTREE_DIRTY: 'WORKTREE_DIRTY',
@@ -54,6 +55,8 @@ export const WORKTREE_ERROR_CODES = Object.freeze({
54
55
  const DEFAULT_ERROR_MESSAGES = Object.freeze({
55
56
  WORKTREE_BUSY_JOBS: 'The session has running jobs; wait for them or stop them before changing its workspace.',
56
57
  WORKTREE_BRANCH_OCCUPIED: 'The branch is checked out in another worktree.',
58
+ WORKTREE_DEFAULT_BRANCH:
59
+ "The repository's default branch can't be opened in a worktree: the project folder could not switch to it while the worktree exists. Check it out in the project folder, or create a new branch for the worktree.",
57
60
  WORKTREE_DISK_INSUFFICIENT: 'Not enough free disk space to create the worktree.',
58
61
  WORKTREE_LARGE_REPO_CONFIRM: 'The repository is large; confirm before creating a worktree.',
59
62
  WORKTREE_DIRTY: 'The worktree has uncommitted changes.',
@@ -193,7 +196,7 @@ function record(value, name) {
193
196
  * @property {string|null} worktreeId Managed worktree id; null when running in the project folder.
194
197
  * @property {string|null} branch Checked-out branch at `cwd`; null when detached or not a repository.
195
198
  * @property {string|null} baseCommit Commit the worktree branch was created from.
196
- * @property {string|null} mergeTarget Branch the worktree merges into (the project folder's branch).
199
+ * @property {string|null} mergeTarget Branch the worktree merges into (the project folder's branch); null for an existing branch until a merge records it.
197
200
  * @property {number} revision Monotonic; advances on every committed workspace change.
198
201
  * @property {WorkspaceTransition} transition In-flight transition marker.
199
202
  */
@@ -132,6 +132,45 @@ async function assertProjectFolder(cwd) {
132
132
  export async function inspectRepo(projectPath, { signal } = {}) {
133
133
  const cwd = canonicalPath(projectPath);
134
134
  await assertProjectFolder(cwd);
135
+ // The usual repository answers every fact in one `rev-parse`: bareness,
136
+ // top level, common dir and HEAD's commit, in that order, then the echoed
137
+ // `--` (which makes an unresolvable HEAD fail the call instead of being
138
+ // read as a path). Any other answer (bare, no work tree, no commits, an
139
+ // unexpected shape) takes the step-by-step reads, whose results and errors
140
+ // are the contract.
141
+ const facts = await runGit(
142
+ cwd,
143
+ [
144
+ 'rev-parse',
145
+ '--is-bare-repository',
146
+ '--show-toplevel',
147
+ '--path-format=absolute',
148
+ '--git-common-dir',
149
+ 'HEAD^{commit}',
150
+ '--',
151
+ ],
152
+ { signal, allowFailure: true }
153
+ );
154
+ const [factBare, factTop, factCommonDir, factHead, dashes, ...rest] = String(facts.stdout || '')
155
+ .split(/\r?\n/)
156
+ .map((line) => line.trim());
157
+ if (
158
+ facts.code === 0 &&
159
+ factBare === 'false' &&
160
+ factTop &&
161
+ factCommonDir &&
162
+ /^[0-9a-f]{40,64}$/.test(factHead) &&
163
+ dashes === '--' &&
164
+ rest.every((line) => line === '')
165
+ ) {
166
+ return {
167
+ projectPath: cwd,
168
+ topLevel: canonicalPath(factTop),
169
+ repoCommonDir: canonicalPath(factCommonDir),
170
+ headCommit: factHead,
171
+ currentBranch: await currentBranch(cwd, { signal }),
172
+ };
173
+ }
135
174
  const probe = await runGit(cwd, ['rev-parse', '--is-bare-repository', '--path-format=absolute', '--git-common-dir'], {
136
175
  signal,
137
176
  allowFailure: true,
@@ -181,11 +220,44 @@ export async function branchCommit(cwd, branch, { signal } = {}) {
181
220
  return result.code === 0 ? firstLine(result.stdout) : null;
182
221
  }
183
222
 
223
+ /** Full commit ids of two existing local branches from one `rev-parse` (the
224
+ * trailing `--` makes a missing one fail the call), or null whenever that
225
+ * answer is not exactly two commit ids: the caller then reads each with
226
+ * branchCommit(). */
227
+ export async function branchCommitPair(cwd, first, second, { signal } = {}) {
228
+ const result = await runGit(cwd, ['rev-parse', `refs/heads/${first}^{commit}`, `refs/heads/${second}^{commit}`, '--'], {
229
+ signal,
230
+ allowFailure: true,
231
+ });
232
+ const [a, b, dashes, ...rest] = String(result.stdout || '')
233
+ .split(/\r?\n/)
234
+ .map((line) => line.trim());
235
+ const commit = /^[0-9a-f]{40,64}$/;
236
+ return result.code === 0 && commit.test(a) && commit.test(b) && dashes === '--' && rest.every((line) => !line)
237
+ ? [a, b]
238
+ : null;
239
+ }
240
+
184
241
  export async function localBranches(cwd, { signal, prefix = '' } = {}) {
185
242
  const result = await runGit(cwd, ['for-each-ref', '--format=%(refname:short)', `refs/heads/${prefix}`], { signal });
186
243
  return lines(result.stdout);
187
244
  }
188
245
 
246
+ /** The local branch names the repository's remotes declare as their default (`refs/remotes/<remote>/HEAD`). */
247
+ export async function remoteDefaultBranches(cwd) {
248
+ const result = await runGit(cwd, ['for-each-ref', '--format=%(refname) %(symref)', 'refs/remotes'], {
249
+ readOnly: true,
250
+ });
251
+ const names = [];
252
+ for (const line of lines(result.stdout)) {
253
+ const [ref, target] = line.split(' ');
254
+ if (!ref.endsWith('/HEAD') || !target) continue;
255
+ const remote = ref.slice(0, -'HEAD'.length);
256
+ if (target.startsWith(remote)) names.push(target.slice(remote.length));
257
+ }
258
+ return names;
259
+ }
260
+
189
261
  export async function validBranchName(cwd, branch) {
190
262
  if (typeof branch !== 'string' || !branch.trim() || branch.startsWith('-')) return false;
191
263
  const result = await runGit(cwd, ['check-ref-format', '--branch', branch], { allowFailure: true });