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.
- package/package.json +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +183 -63
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +91 -66
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +124 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -5
- package/scripts/suite-health.mjs +115 -24
- package/scripts/test-data-dir.mjs +10 -7
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/app.mjs +4 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +23 -2
- package/src/defaults/skills/browser-use/SKILL.md +10 -5
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +16 -4
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +35 -42
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +12 -7
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +8 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +29 -11
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +11 -2
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +113 -36
- package/src/runtime/shared/tool-work-units.mjs +6 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +72 -0
- package/src/runtime/shared/worktrees/manager.mjs +285 -168
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +6 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +458 -312
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +7 -1
- package/src/session-runtime/bot-status.mjs +107 -101
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +401 -36
- package/src/session-runtime/conversation-import/service.mjs +35 -8
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +283 -11
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1041
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +169 -138
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +10 -4
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +29 -18
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1963 -1361
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/ui/statusline-agents.mjs +1 -3
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -15,51 +15,28 @@
|
|
|
15
15
|
* (runtime/shared/worktrees/manager.mjs), loaded on first use.
|
|
16
16
|
*/
|
|
17
17
|
import { randomUUID } from 'node:crypto';
|
|
18
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
19
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
20
|
-
import {
|
|
21
|
-
WORKTREE_CARRY_CHOICES,
|
|
22
|
-
WORKTREE_SNAPSHOT_KIND,
|
|
23
|
-
branchMenuEntries,
|
|
24
|
-
isWorktreeBound,
|
|
25
|
-
isWorktreeError,
|
|
26
|
-
normalizeSessionWorkspace,
|
|
27
|
-
worktreeError,
|
|
28
|
-
worktreeNameError,
|
|
29
|
-
worktreeRefusal,
|
|
30
|
-
} from '../../runtime/shared/worktrees/contract.mjs';
|
|
31
18
|
import { normalizeWorktreeSettings } from '../../runtime/shared/worktrees/settings.mjs';
|
|
32
19
|
import * as maintenance from '../../runtime/shared/worktrees/maintenance.mjs';
|
|
33
|
-
import { inspectRepo
|
|
34
|
-
import {
|
|
35
|
-
import { isWithin, samePath } from '../../runtime/shared/worktrees/paths.mjs';
|
|
36
|
-
import { readGitHead } from '../../runtime/shared/worktrees/git-head.mjs';
|
|
37
|
-
import {
|
|
38
|
-
holdWorkspaceActivity,
|
|
39
|
-
isExternalCheckout,
|
|
40
|
-
releaseAll,
|
|
41
|
-
} from '../../runtime/shared/worktrees/activity-gate.mjs';
|
|
42
|
-
import { mergeConfirmedTarget } from '../../runtime/shared/worktrees/branch-management.mjs';
|
|
20
|
+
import { inspectRepo } from '../../runtime/shared/worktrees/git.mjs';
|
|
21
|
+
import { samePath } from '../../runtime/shared/worktrees/paths.mjs';
|
|
43
22
|
import {
|
|
44
23
|
clearReadDedupSession,
|
|
45
24
|
resetReadStateAfterCompaction,
|
|
46
25
|
} from '../../runtime/agent/orchestrator/session/cache/read-cache.mjs';
|
|
47
|
-
import { sessionHasConversationMessages } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
48
26
|
import { buildWorkspacePromptContext } from '../../runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs';
|
|
49
|
-
import {
|
|
50
|
-
promptContentText,
|
|
51
|
-
refreshSessionWorkspaceContext,
|
|
52
|
-
} from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
|
|
27
|
+
import { refreshSessionWorkspaceContext } from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
|
|
53
28
|
import { AGENT_OWNER } from '../../runtime/agent/orchestrator/agent-owner.mjs';
|
|
54
29
|
import { _liveSessions } from '../../runtime/agent/orchestrator/session/store/live-state.mjs';
|
|
55
|
-
import {
|
|
30
|
+
import { createActivityGuards } from './activity-guards.mjs';
|
|
56
31
|
import { createProjectBranchOps } from './project-branches.mjs';
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
32
|
+
import { createSessionWorkspaceView } from './session-view.mjs';
|
|
33
|
+
import { createSideCheckouts } from './side-checkouts.mjs';
|
|
34
|
+
import { createTransitionRunner } from './transition-runner.mjs';
|
|
35
|
+
import { createTransitionSteps } from './transition-steps.mjs';
|
|
36
|
+
import { createWorkspaceMerge } from './workspace-merge.mjs';
|
|
37
|
+
import { createWorktreeIntents, creationIntent } from './worktree-intents.mjs';
|
|
38
|
+
import { createWorktreeMaintenance } from './worktree-maintenance.mjs';
|
|
39
|
+
import { createWorktreeSetup, defaultPlanWorktreeSetup, defaultRunWorktreeSetup } from './worktree-setup.mjs';
|
|
63
40
|
|
|
64
41
|
const loadDefaultManager = () => import('../../runtime/shared/worktrees/manager.mjs');
|
|
65
42
|
|
|
@@ -71,28 +48,12 @@ async function defaultOwnShellJobCount(sessionIds) {
|
|
|
71
48
|
return otherSessionBackgroundShellJobCount(new Set()) - otherSessionBackgroundShellJobCount(new Set(sessionIds));
|
|
72
49
|
}
|
|
73
50
|
|
|
74
|
-
const lower = (value) => String(value || '').toLowerCase();
|
|
75
|
-
|
|
76
|
-
const WORKTREE_BOUND_SWITCH =
|
|
77
|
-
'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.';
|
|
78
|
-
|
|
79
51
|
/** The registered Project (projects.json) at `path`, or null. */
|
|
80
52
|
async function defaultRegisteredProject(path) {
|
|
81
53
|
const { listProjectsStrict } = await import('../../runtime/shared/projects.mjs');
|
|
82
54
|
return listProjectsStrict().find((entry) => samePath(entry.path, path))?.path ?? null;
|
|
83
55
|
}
|
|
84
56
|
|
|
85
|
-
/** The dependency installs a new worktree folder still needs (dependencies.mjs). */
|
|
86
|
-
async function defaultPlanWorktreeSetup(input) {
|
|
87
|
-
const { planDependencySetup } = await import('../../runtime/shared/worktrees/dependencies.mjs');
|
|
88
|
-
return planDependencySetup(input);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function defaultRunWorktreeSetup(input) {
|
|
92
|
-
const { runWorktreeSetupCommands } = await import('../../runtime/shared/worktrees/setup-commands.mjs');
|
|
93
|
-
return runWorktreeSetupCommands(input);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
57
|
/**
|
|
97
58
|
* Re-root one session's prompt at `cwd`: environment facts and instruction
|
|
98
59
|
* files for every session, project memory for the Lead only (workers never
|
|
@@ -120,58 +81,6 @@ export async function refreshWorkspacePrompt(session, cwd, { loadCoreMemoryConte
|
|
|
120
81
|
});
|
|
121
82
|
}
|
|
122
83
|
|
|
123
|
-
/**
|
|
124
|
-
* Creation-time intent: `{ mode: 'worktree', name }` asks for a worktree;
|
|
125
|
-
* `{ mode: 'default' }` — an interactive Lead the user starts (TUI launch) —
|
|
126
|
-
* follows the settings `newTaskDefault`; `{ mode: 'branch' }` or no intent
|
|
127
|
-
* runs in the project folder. Desktop tasks carry their choice from the
|
|
128
|
-
* renderer, and agents, schedules, webhooks and resumed sessions pass none,
|
|
129
|
-
* so the default never applies to them.
|
|
130
|
-
*/
|
|
131
|
-
function creationIntent(worktree, settings) {
|
|
132
|
-
if (worktree?.mode === 'worktree') {
|
|
133
|
-
return { name: worktree.name ?? null, confirmLarge: false, explicit: true, sessionId: null, txId: null };
|
|
134
|
-
}
|
|
135
|
-
return worktree?.mode === 'default' && settings.newTaskDefault === 'worktree'
|
|
136
|
-
? { name: null, confirmLarge: false, explicit: false, sessionId: null, txId: null }
|
|
137
|
-
: null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function validName(name) {
|
|
141
|
-
if (name === undefined || name === null) return null;
|
|
142
|
-
const error = worktreeNameError(name);
|
|
143
|
-
if (error) throw new TypeError(`worktree name is invalid: ${error}`);
|
|
144
|
-
return name;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function normalizeTarget(target) {
|
|
148
|
-
const kind = target?.kind;
|
|
149
|
-
if (kind === 'worktree') return { kind, name: validName(target.name) };
|
|
150
|
-
if (kind === 'branch') {
|
|
151
|
-
if (typeof target.branch !== 'string' || !target.branch.trim()) {
|
|
152
|
-
throw new TypeError('workspace target branch must be a non-empty string.');
|
|
153
|
-
}
|
|
154
|
-
return { kind, branch: target.branch.trim(), create: target.create === true };
|
|
155
|
-
}
|
|
156
|
-
if (kind === 'remove-worktree') return { kind, force: target.force === true };
|
|
157
|
-
throw new TypeError('workspace target kind must be branch, worktree or remove-worktree.');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function validRevision(value) {
|
|
161
|
-
if (value === undefined || value === null) return undefined;
|
|
162
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
163
|
-
throw new TypeError('expectedRevision must be a non-negative integer.');
|
|
164
|
-
}
|
|
165
|
-
return value;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** WORKTREE_REVISION_CONFLICT when the caller expected another workspace revision. */
|
|
169
|
-
function assertRevision(revision, expectedRevision) {
|
|
170
|
-
if (expectedRevision !== undefined && expectedRevision !== revision) {
|
|
171
|
-
throw worktreeError('WORKTREE_REVISION_CONFLICT', undefined, { expectedRevision, revision });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
84
|
/**
|
|
176
85
|
* The workspace methods every external caller (the runtime facade: desktop
|
|
177
86
|
* IPC, the daemon's `session.configure` lane, remote views) reaches: exactly
|
|
@@ -190,12 +99,6 @@ export function publicWorkspaceMethods(api) {
|
|
|
190
99
|
};
|
|
191
100
|
}
|
|
192
101
|
|
|
193
|
-
// A settled turn's snapshot cleanup waits at most 2^5 times its first delay between attempts.
|
|
194
|
-
const SNAPSHOT_BACKOFF_STEPS = 5;
|
|
195
|
-
|
|
196
|
-
/** The in-flight marker state of each transition kind. */
|
|
197
|
-
const TRANSITION_STATE = { worktree: 'provisioning', branch: 'switching', 'remove-worktree': 'removing' };
|
|
198
|
-
|
|
199
102
|
/**
|
|
200
103
|
* @param {object} deps
|
|
201
104
|
* @param {() => any} deps.getSession The Lead session this runtime hosts.
|
|
@@ -247,15 +150,7 @@ export function createWorkspaceTransitions({
|
|
|
247
150
|
snapshotRetryLimit = 20,
|
|
248
151
|
}) {
|
|
249
152
|
const worktreeSettings = () => normalizeWorktreeSettings(getConfig?.()?.worktree);
|
|
250
|
-
|
|
251
|
-
// The creation txIds of the read-only snapshots the current admitted turn
|
|
252
|
-
// made. Each turn owns its own set, so its cleanup never touches a later
|
|
253
|
-
// turn's snapshots (a txId, unlike a freed slug, is never reused).
|
|
254
|
-
let turnSnapshots = null;
|
|
255
|
-
// The running cleanups of settled turns' snapshots (controller → session id).
|
|
256
|
-
const snapshotCleanups = new Map();
|
|
257
|
-
let disposed = false;
|
|
258
|
-
const setupListeners = new Set();
|
|
153
|
+
const initialPending = creationIntent(initialIntent, worktreeSettings());
|
|
259
154
|
let managerPromise = null;
|
|
260
155
|
const backend = () => {
|
|
261
156
|
if (manager) return Promise.resolve(manager);
|
|
@@ -263,209 +158,28 @@ export function createWorkspaceTransitions({
|
|
|
263
158
|
return managerPromise;
|
|
264
159
|
};
|
|
265
160
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/** The stored record, or the project-folder workspace (revision 0) of a session that never moved. */
|
|
287
|
-
function currentWorkspace(session) {
|
|
288
|
-
const stored = readSessionWorkspace(session);
|
|
289
|
-
if (stored) return stored;
|
|
290
|
-
const projectPath = projectPathOf(session);
|
|
291
|
-
return normalizeSessionWorkspace({ projectPath, cwd: getCurrentCwd() || projectPath, revision: 0 });
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
let liveWorkspace = null;
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* The workspace the session publishes: a worktree-bound record as stored; a
|
|
298
|
-
* project-folder session (never moved, back from a worktree, or carrying a
|
|
299
|
-
* transition stub) with the folder's checked-out branch / detached commit and
|
|
300
|
-
* repoCommonDir read straight from HEAD — no git process. Null outside a git
|
|
301
|
-
* repository for a session without a record. Identity-stable while equal.
|
|
302
|
-
*/
|
|
303
|
-
function getLiveWorkspace() {
|
|
304
|
-
const session = getSession();
|
|
305
|
-
const stored = readSessionWorkspace(session);
|
|
306
|
-
if (isWorktreeBound(stored)) return session.workspace;
|
|
307
|
-
let base;
|
|
308
|
-
try {
|
|
309
|
-
base = stored ?? currentWorkspace(session);
|
|
310
|
-
} catch {
|
|
311
|
-
return session?.workspace ?? null;
|
|
312
|
-
}
|
|
313
|
-
const head = readGitHead(base.cwd);
|
|
314
|
-
if (!head) return session?.workspace ?? null;
|
|
315
|
-
const next = {
|
|
316
|
-
...base,
|
|
317
|
-
repoCommonDir: head.repoCommonDir,
|
|
318
|
-
branch: head.branch,
|
|
319
|
-
baseCommit: head.branch ? base.baseCommit : head.commit,
|
|
320
|
-
};
|
|
321
|
-
if (!liveWorkspace || !isDeepStrictEqual(liveWorkspace, next)) liveWorkspace = next;
|
|
322
|
-
return liveWorkspace;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function isSessionActive(session) {
|
|
326
|
-
const runtime = sessionManager?.getSessionRuntime?.(session.id);
|
|
327
|
-
if (runtime?.controller?.signal && !runtime.controller.signal.aborted) return true;
|
|
328
|
-
return ACTIVE_STAGES.has(lower(runtime?.stage || session.status));
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const workersOf = (session) =>
|
|
332
|
-
listLiveSessions().filter(
|
|
333
|
-
(candidate) =>
|
|
334
|
-
candidate?.id &&
|
|
335
|
-
candidate.id !== session.id &&
|
|
336
|
-
candidate.owner === AGENT_OWNER &&
|
|
337
|
-
candidate.closed !== true &&
|
|
338
|
-
(candidate.ownerSessionId === session.id || candidate.parentSessionId === session.id)
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* WORKTREE_BUSY_JOBS while this Lead has a turn, workers or background
|
|
343
|
-
* shells running (`shellJobs: false` tolerates background shells).
|
|
344
|
-
*/
|
|
345
|
-
async function assertLeadIdle(session, { ownTurn = false, shellJobs: checkShellJobs = true } = {}) {
|
|
346
|
-
if (!ownTurn && getActiveTurnCount() > 0) {
|
|
347
|
-
throw worktreeError('WORKTREE_BUSY_JOBS', 'The session is running a turn; wait for it to finish.', {
|
|
348
|
-
reason: 'turn',
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
const status = agentStatusState?.() || {};
|
|
352
|
-
const workers = Array.isArray(status.agentWorkers) ? status.agentWorkers : [];
|
|
353
|
-
const jobs = Array.isArray(status.agentJobs) ? status.agentJobs : [];
|
|
354
|
-
const busyWorkers = workers.filter(
|
|
355
|
-
(row) => BUSY_WORKER_STATES.has(lower(row?.status)) || BUSY_WORKER_STATES.has(lower(row?.stage))
|
|
356
|
-
).length;
|
|
357
|
-
const busyJobs = jobs.filter((row) => BUSY_JOB_STATES.has(lower(row?.status))).length;
|
|
358
|
-
if (busyWorkers || busyJobs) {
|
|
359
|
-
throw worktreeError('WORKTREE_BUSY_JOBS', undefined, { reason: 'workers', workers: busyWorkers, jobs: busyJobs });
|
|
360
|
-
}
|
|
361
|
-
if (!checkShellJobs) return;
|
|
362
|
-
const ids = new Set([session.id, ...workers.map((row) => row?.sessionId).filter(Boolean)]);
|
|
363
|
-
for (const worker of workersOf(session)) ids.add(worker.id);
|
|
364
|
-
const shellJobs = await countOwnShellJobs([...ids]);
|
|
365
|
-
if (shellJobs > 0) throw worktreeError('WORKTREE_BUSY_JOBS', undefined, { reason: 'shell-jobs', shellJobs });
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* A checkout or merge in the shared checkout `folder` refuses while another
|
|
370
|
-
* session (not this Lead's own workers) works there, then holds the folder
|
|
371
|
-
* until the returned release: other sessions' turns that arrive meanwhile
|
|
372
|
-
* wait (admission gate). `sessionId` null: a session-less (draft) change.
|
|
373
|
-
* @returns {() => void}
|
|
374
|
-
*/
|
|
375
|
-
function holdProjectFolder(sessionId, folder, { hold = true } = {}) {
|
|
376
|
-
const users = listLiveSessions().filter(
|
|
377
|
-
(candidate) =>
|
|
378
|
-
candidate?.id &&
|
|
379
|
-
candidate.id !== sessionId &&
|
|
380
|
-
candidate.closed !== true &&
|
|
381
|
-
(!sessionId || (candidate.ownerSessionId !== sessionId && candidate.parentSessionId !== sessionId)) &&
|
|
382
|
-
typeof candidate.cwd === 'string' &&
|
|
383
|
-
isWithin(folder, candidate.cwd) &&
|
|
384
|
-
isSessionActive(candidate)
|
|
385
|
-
);
|
|
386
|
-
if (users.length) throw worktreeRefusal('folder-in-use', { sessions: users.map((candidate) => candidate.id) });
|
|
387
|
-
return holdCheckoutFolder(sessionId, folder, { hold });
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** Run `fn` holding the checkout `folder` (holdProjectFolder); released when it settles. */
|
|
391
|
-
async function withProjectFolder(sessionId, folder, fn) {
|
|
392
|
-
const release = holdProjectFolder(sessionId, folder);
|
|
393
|
-
try {
|
|
394
|
-
return await fn();
|
|
395
|
-
} finally {
|
|
396
|
-
release();
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Before a carry clears the shared checkout `folder` (held by the caller,
|
|
402
|
-
* so no new turn starts there): WORKTREE_BUSY_JOBS while any other session
|
|
403
|
-
* working in it — idle or not, this Lead's workers included — owns a
|
|
404
|
-
* running background shell (holdProjectFolder already refused running
|
|
405
|
-
* turns and agents there, assertLeadIdle this Lead's own). Processes
|
|
406
|
-
* outside Mixdog (editors, watchers) cannot be excluded.
|
|
407
|
-
*/
|
|
408
|
-
async function assertCheckoutQuiet(session, folder) {
|
|
409
|
-
const sharing = listLiveSessions()
|
|
410
|
-
.filter(
|
|
411
|
-
(candidate) =>
|
|
412
|
-
candidate?.id &&
|
|
413
|
-
candidate.id !== session.id &&
|
|
414
|
-
candidate.closed !== true &&
|
|
415
|
-
typeof candidate.cwd === 'string' &&
|
|
416
|
-
isWithin(folder, candidate.cwd)
|
|
417
|
-
)
|
|
418
|
-
.map((candidate) => candidate.id);
|
|
419
|
-
if (!sharing.length) return;
|
|
420
|
-
const shellJobs = await countOwnShellJobs(sharing);
|
|
421
|
-
if (shellJobs > 0) throw worktreeRefusal('folder-shell-jobs', { shellJobs });
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* The shared admission of every merge and folder cleanup (manageProjectBranch):
|
|
426
|
-
* refuses while a session or worker runs in an affected folder, or a
|
|
427
|
-
* background command does; `removing` refuses while any session still uses
|
|
428
|
-
* one. Then holds the folders — Mixdog checkouts only; an external checkout
|
|
429
|
-
* takes no lease from this profile — so new Mixdog work there waits until `run`
|
|
430
|
-
* settles. `actor`: the session acting on its own checkout, whose own turn
|
|
431
|
-
* and workers its caller already checked (assertLeadIdle).
|
|
432
|
-
*/
|
|
433
|
-
async function withManagementFolders(folders, removing, run, { actor = null } = {}) {
|
|
434
|
-
const release = [];
|
|
435
|
-
try {
|
|
436
|
-
for (const folder of [...new Set(folders)].sort()) {
|
|
437
|
-
release.push(holdProjectFolder(actor, folder, { hold: !isExternalCheckout(folder) }));
|
|
438
|
-
}
|
|
439
|
-
release.push(holdWorkspaceActivity(folders, { sessionId: actor }));
|
|
440
|
-
const stored = await listWorkspaceConsumers();
|
|
441
|
-
const consumers = new Map(stored.map((session) => [session.id, session]));
|
|
442
|
-
for (const session of listLiveSessions()) if (session?.closed !== true) consumers.set(session.id, session);
|
|
443
|
-
const affected = [...consumers.values()].filter((session) =>
|
|
444
|
-
folders.some((folder) => session.cwd && isWithin(folder, session.cwd))
|
|
445
|
-
);
|
|
446
|
-
const blocking = removing
|
|
447
|
-
? affected
|
|
448
|
-
: affected.filter(
|
|
449
|
-
(session) =>
|
|
450
|
-
!(actor && [session.id, session.ownerSessionId, session.parentSessionId].includes(actor)) &&
|
|
451
|
-
isSessionActive(session)
|
|
452
|
-
);
|
|
453
|
-
if (blocking.length) throw worktreeRefusal('folder-in-use', { sessions: blocking.map((session) => session.id) });
|
|
454
|
-
const jobs = await listWorkspaceJobs();
|
|
455
|
-
if (jobs.some((job) => !job.terminal && folders.some((folder) => isWithin(folder, job.cwd)))) {
|
|
456
|
-
throw worktreeRefusal('folder-shell-jobs');
|
|
457
|
-
}
|
|
458
|
-
if ((await countOwnShellJobs(affected.map((session) => session.id))) > 0) {
|
|
459
|
-
throw worktreeRefusal('folder-shell-jobs');
|
|
460
|
-
}
|
|
461
|
-
return await run([...consumers.keys()]);
|
|
462
|
-
} finally {
|
|
463
|
-
releaseAll(release);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
161
|
+
const view = createSessionWorkspaceView({
|
|
162
|
+
getSession,
|
|
163
|
+
getCurrentCwd,
|
|
164
|
+
getDesktopSession,
|
|
165
|
+
createCurrentSession,
|
|
166
|
+
inspectProject,
|
|
167
|
+
backend,
|
|
168
|
+
pendingWorktree: () => intents.pendingWorktree(),
|
|
169
|
+
});
|
|
170
|
+
const { hostedSession, ensureSession, currentWorkspace } = view;
|
|
171
|
+
const guards = createActivityGuards({
|
|
172
|
+
getActiveTurnCount,
|
|
173
|
+
agentStatusState,
|
|
174
|
+
countOwnShellJobs,
|
|
175
|
+
listLiveSessions,
|
|
176
|
+
sessionManager,
|
|
177
|
+
listWorkspaceConsumers,
|
|
178
|
+
listWorkspaceJobs,
|
|
179
|
+
});
|
|
180
|
+
const { assertLeadIdle, withProjectFolder, withManagementFolders } = guards;
|
|
467
181
|
const steps = createTransitionSteps({
|
|
468
|
-
assertCheckoutQuiet,
|
|
182
|
+
assertCheckoutQuiet: guards.assertCheckoutQuiet,
|
|
469
183
|
backend,
|
|
470
184
|
inspectProject,
|
|
471
185
|
runtimeToken,
|
|
@@ -477,7 +191,7 @@ export function createWorkspaceTransitions({
|
|
|
477
191
|
refreshWorkspaceContext,
|
|
478
192
|
invalidateCaches,
|
|
479
193
|
updateLeadWorkerCwd,
|
|
480
|
-
workersOf,
|
|
194
|
+
workersOf: guards.workersOf,
|
|
481
195
|
withProjectFolder,
|
|
482
196
|
});
|
|
483
197
|
const projectBranches = createProjectBranchOps({
|
|
@@ -487,740 +201,93 @@ export function createWorkspaceTransitions({
|
|
|
487
201
|
withProjectFolder,
|
|
488
202
|
withManagementFolders,
|
|
489
203
|
});
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
* The one entry of every transition. `ownTurn` (internal: the Lead's own
|
|
545
|
-
* tool call) tolerates the calling turn; `txId` keys the creation.
|
|
546
|
-
*/
|
|
547
|
-
async function runTransition(sessionRef, target, options = {}, { ownTurn = false, txId = '' } = {}) {
|
|
548
|
-
const session = hostedSession(sessionRef);
|
|
549
|
-
return transition(session, normalizeTarget(target), {
|
|
550
|
-
expectedRevision: validRevision(options.expectedRevision),
|
|
551
|
-
confirmLarge: options.confirmLarge === true,
|
|
552
|
-
signal: options.signal,
|
|
553
|
-
ownTurn: ownTurn === true,
|
|
554
|
-
txId,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
204
|
+
const runner = createTransitionRunner({
|
|
205
|
+
getSession,
|
|
206
|
+
hostedSession,
|
|
207
|
+
currentWorkspace,
|
|
208
|
+
assertLeadIdle,
|
|
209
|
+
steps,
|
|
210
|
+
newTxId,
|
|
211
|
+
applyResolvedCwd,
|
|
212
|
+
getCurrentCwd,
|
|
213
|
+
});
|
|
214
|
+
const setup = createWorktreeSetup({ backend, newTxId, planWorktreeSetup, runWorktreeSetup });
|
|
215
|
+
const intents = createWorktreeIntents({
|
|
216
|
+
initial: initialPending,
|
|
217
|
+
getSession,
|
|
218
|
+
getReservedSessionId,
|
|
219
|
+
ensureSession,
|
|
220
|
+
currentWorkspace,
|
|
221
|
+
backend,
|
|
222
|
+
transition: runner.transition,
|
|
223
|
+
runTransition: runner.runTransition,
|
|
224
|
+
runPendingSetup: setup.runPendingSetup,
|
|
225
|
+
newTxId,
|
|
226
|
+
});
|
|
227
|
+
const checkouts = createSideCheckouts({
|
|
228
|
+
getSession,
|
|
229
|
+
getCurrentCwd,
|
|
230
|
+
hostedSession,
|
|
231
|
+
sessionProject: view.sessionProject,
|
|
232
|
+
backend,
|
|
233
|
+
runtimeToken,
|
|
234
|
+
newTxId,
|
|
235
|
+
snapshotRetryMs,
|
|
236
|
+
snapshotRetryLimit,
|
|
237
|
+
});
|
|
238
|
+
const worktrees = createWorktreeMaintenance({
|
|
239
|
+
getSession,
|
|
240
|
+
listLiveSessions,
|
|
241
|
+
listStoredSessionIds,
|
|
242
|
+
backend,
|
|
243
|
+
withManagementFolders,
|
|
244
|
+
cancelSnapshotCleanups: checkouts.cancelSnapshotCleanups,
|
|
245
|
+
worktreeSettings,
|
|
246
|
+
});
|
|
247
|
+
const merge = createWorkspaceMerge({
|
|
248
|
+
hostedSession,
|
|
249
|
+
currentWorkspace,
|
|
250
|
+
assertLeadIdle,
|
|
251
|
+
inspectProject,
|
|
252
|
+
backend,
|
|
253
|
+
withManagementFolders,
|
|
254
|
+
removeWorktree: runner.removeWorktree,
|
|
255
|
+
worktreeSettings,
|
|
256
|
+
newTxId,
|
|
257
|
+
});
|
|
557
258
|
|
|
558
259
|
/** @type {import('../../runtime/shared/worktrees/contract.mjs').ApplyWorkspaceTransition} */
|
|
559
260
|
async function applyWorkspaceTransition(sessionRef, target, options = {}) {
|
|
560
|
-
return runTransition(sessionRef, target, options);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/** The pending worktree intent that applies to `session`'s next turn, if any. */
|
|
564
|
-
function intentFor(session) {
|
|
565
|
-
const intent = pendingIntent;
|
|
566
|
-
if (!intent) return null;
|
|
567
|
-
if (isWorktreeBound(readSessionWorkspace(session))) {
|
|
568
|
-
pendingIntent = null;
|
|
569
|
-
return null;
|
|
570
|
-
}
|
|
571
|
-
if (intent.sessionId) return intent.sessionId === session.id ? intent : null;
|
|
572
|
-
// A creation-time intent belongs to a new task: never to a resumed
|
|
573
|
-
// conversation this runtime opened instead.
|
|
574
|
-
const started =
|
|
575
|
-
sessionHasConversationMessages(session) || sessionHasConversationMessages({ messages: session.liveTurnMessages });
|
|
576
|
-
return started ? null : intent;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Create and enter the worktree `intent` asks for now: its txId is fixed on
|
|
581
|
-
* first use (`submissionId`, else a fresh one) so a retry reuses the same
|
|
582
|
-
* worktree, and the prompt text names an unnamed one. A settled intent is
|
|
583
|
-
* consumed.
|
|
584
|
-
*/
|
|
585
|
-
async function provisionIntent(session, intent, submissionId, prompt, options) {
|
|
586
|
-
intent.txId ||= String(submissionId || '').trim() || newTxId();
|
|
587
|
-
const result = await transition(
|
|
588
|
-
session,
|
|
589
|
-
{ kind: 'worktree', name: intent.name },
|
|
590
|
-
{ txId: intent.txId, nameHint: promptContentText(prompt), confirmLarge: intent.confirmLarge, ...options }
|
|
591
|
-
);
|
|
592
|
-
if (pendingIntent === intent) pendingIntent = null;
|
|
593
|
-
return result;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* Turn-start workspace gate, run after the session exists and before the
|
|
598
|
-
* user row, the review snapshot, hooks, the provider and any worker spawn.
|
|
599
|
-
* A session in recovery refuses the turn; a pending worktree intent is
|
|
600
|
-
* provisioned now (txId = the first submission id, reused by retries; an
|
|
601
|
-
* unnamed worktree takes its slug from the first message).
|
|
602
|
-
*/
|
|
603
|
-
async function prepareTurnWorkspace({ submissionId = '', prompt = '', signal, confirmLarge = false } = {}) {
|
|
604
|
-
const session = getSession();
|
|
605
|
-
if (!session?.id) return null;
|
|
606
|
-
const stored = readSessionWorkspace(session);
|
|
607
|
-
if (stored?.transition.state === 'recovery') {
|
|
608
|
-
throw worktreeError(
|
|
609
|
-
'WORKTREE_MISSING',
|
|
610
|
-
'The worktree for this session no longer exists; pick a branch or remove the worktree to continue.',
|
|
611
|
-
{ worktreeId: stored.worktreeId, path: stored.cwd }
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
const intent = intentFor(session);
|
|
615
|
-
let result = null;
|
|
616
|
-
if (intent) {
|
|
617
|
-
try {
|
|
618
|
-
result = await provisionIntent(session, intent, submissionId, prompt, {
|
|
619
|
-
confirmLarge: intent.confirmLarge || confirmLarge === true,
|
|
620
|
-
signal,
|
|
621
|
-
ownTurn: true,
|
|
622
|
-
});
|
|
623
|
-
} catch (error) {
|
|
624
|
-
// The settings default never blocks a task outside a git repository.
|
|
625
|
-
if (!intent.explicit && isWorktreeError(error, 'WORKTREE_UNSUPPORTED_REPO')) {
|
|
626
|
-
if (pendingIntent === intent) pendingIntent = null;
|
|
627
|
-
return null;
|
|
628
|
-
}
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
await runPendingSetup(session, signal);
|
|
633
|
-
return result;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* Prepare the session's new worktree, once, under the turn (its admission
|
|
638
|
-
* keeps the folder from being merged or removed; its signal — the Stop
|
|
639
|
-
* button — cancels): the frozen installs the links made at creation did
|
|
640
|
-
* not cover. While an install runs, every onWorktreeSetup listener gets its
|
|
641
|
-
* one-line progress; nothing else reaches the user. The agent gets one
|
|
642
|
-
* compact note with this turn (`session.pendingSetupNotice`): the main
|
|
643
|
-
* checkout, what was copied, linked and installed, what failed (with the
|
|
644
|
-
* output tail) and what was skipped. A failure or timeout is in the note
|
|
645
|
-
* and the turn goes on; a cancel aborts the turn like any other stop. The
|
|
646
|
-
* worktree stays either way. The setup belongs to the worktree it was made
|
|
647
|
-
* for, whichever path made it (a worktree task, a branch selection, `git
|
|
648
|
-
* worktree add`): its registry entry keeps it pending (manager
|
|
649
|
-
* claimWorktreeSetup) until the first turn a session runs there, never in
|
|
650
|
-
* another folder.
|
|
651
|
-
*/
|
|
652
|
-
async function runPendingSetup(session, signal) {
|
|
653
|
-
const here = readSessionWorkspace(session);
|
|
654
|
-
if (!isWorktreeBound(here)) return;
|
|
655
|
-
const claimed = await (await backend()).claimWorktreeSetup(here.worktreeId);
|
|
656
|
-
if (!claimed) return;
|
|
657
|
-
const id = newTxId();
|
|
658
|
-
const report = {
|
|
659
|
-
root: claimed.prepared?.root || here.projectPath,
|
|
660
|
-
prepared: claimed.prepared,
|
|
661
|
-
dependencyNotes: [],
|
|
662
|
-
installs: null,
|
|
663
|
-
notes: [],
|
|
664
|
-
};
|
|
665
|
-
const emit = (event) => {
|
|
666
|
-
for (const listener of setupListeners) {
|
|
667
|
-
try {
|
|
668
|
-
listener({ ...event, id, sessionId: session.id, cwd: here.cwd });
|
|
669
|
-
} catch {
|
|
670
|
-
/* a viewer's failure never stops the setup */
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
try {
|
|
675
|
-
const plan = await planWorktreeSetup({ worktreeDir: here.cwd, mainDir: here.projectPath, signal });
|
|
676
|
-
report.dependencyNotes = plan.notes;
|
|
677
|
-
if (plan.commands.length) {
|
|
678
|
-
report.installs = await runWorktreeSetup({ commands: plan.commands, signal, onProgress: emit });
|
|
679
|
-
}
|
|
680
|
-
} catch (error) {
|
|
681
|
-
if (!signal?.aborted) report.notes.push(`Dependencies could not be prepared: ${error?.message || error}`);
|
|
682
|
-
}
|
|
683
|
-
session.pendingSetupNotice = { revision: Date.now(), content: worktreePreparationNote(report) };
|
|
684
|
-
throwIfAborted(signal);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/** Subscribe to worktree setup progress; returns the unsubscribe. */
|
|
688
|
-
function onWorktreeSetup(listener) {
|
|
689
|
-
if (typeof listener !== 'function') return () => {};
|
|
690
|
-
setupListeners.add(listener);
|
|
691
|
-
return () => setupListeners.delete(listener);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
async function getBranchMenu() {
|
|
695
|
-
const session = getSession();
|
|
696
|
-
const workspace = session?.id
|
|
697
|
-
? currentWorkspace(session)
|
|
698
|
-
: normalizeSessionWorkspace({ projectPath: projectPathOf(null), cwd: getCurrentCwd(), revision: 0 });
|
|
699
|
-
const menu = await (await backend()).listBranchOccupancy({
|
|
700
|
-
projectPath: workspace.projectPath,
|
|
701
|
-
sessionId: session?.id || null,
|
|
702
|
-
});
|
|
703
|
-
const selectedEntry = workspace.worktreeId ? await (await backend()).findWorktreeContaining(workspace.cwd) : null;
|
|
704
|
-
// A session outside a managed worktree (the project folder — never moved,
|
|
705
|
-
// or back from a worktree — or a linked checkout of the selected branch)
|
|
706
|
-
// is on whatever that folder has checked out now.
|
|
707
|
-
// Its repository is the project's (a never-moved session has no stored
|
|
708
|
-
// record), and a detached checkout shows branch null with the commit in
|
|
709
|
-
// baseCommit — never mistaken for "not a repository".
|
|
710
|
-
let view = workspace;
|
|
711
|
-
if (!isWorktreeBound(workspace)) {
|
|
712
|
-
const project = await inspectProject(workspace.cwd);
|
|
713
|
-
const checkedOut = project.currentBranch ?? null;
|
|
714
|
-
view = {
|
|
715
|
-
...workspace,
|
|
716
|
-
repoCommonDir: project.repoCommonDir ?? workspace.repoCommonDir,
|
|
717
|
-
branch: checkedOut,
|
|
718
|
-
baseCommit: checkedOut ? workspace.baseCommit : (project.headCommit ?? null),
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
return {
|
|
722
|
-
workspace: view,
|
|
723
|
-
pendingWorktree: pendingIntent ? { name: pendingIntent.name } : null,
|
|
724
|
-
branches: branchMenuEntries(menu.branches, menu.occupancy),
|
|
725
|
-
occupancy: menu.occupancy,
|
|
726
|
-
retainedWorktree: selectedEntry ?? menu.retainedWorktree ?? null,
|
|
727
|
-
// Creation notes of the retained worktree (submodules, include entries).
|
|
728
|
-
warnings: Array.isArray(menu.retainedWorktree?.warnings) ? [...menu.retainedWorktree.warnings] : [],
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/** The maintenance scope here: every live session of this process counts as a live owner. */
|
|
733
|
-
async function maintenanceScope() {
|
|
734
|
-
const liveSessionIds = [
|
|
735
|
-
...new Set([getSession()?.id, ...listLiveSessions().map((candidate) => candidate?.id)].filter(Boolean)),
|
|
736
|
-
];
|
|
737
|
-
return { liveSessionIds, listSessionIds: listStoredSessionIds, manager: await backend() };
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
* Every managed worktree with who retains it (the manual list). Read-only
|
|
742
|
-
* snapshots (no branch) only with `includeSnapshots` (the git tool); the
|
|
743
|
-
* desktop list (DesktopManagedWorktreeRow, a branch per row) never gets them.
|
|
744
|
-
*/
|
|
745
|
-
async function listWorktrees({ includeSnapshots = false } = {}) {
|
|
746
|
-
const rows = await maintenance.listWorktreesWithRetention(await maintenanceScope());
|
|
747
|
-
return includeSnapshots ? rows : rows.filter((row) => row.kind !== WORKTREE_SNAPSHOT_KIND);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/** The manual prune: clean, merged worktrees no stored or live session retains. */
|
|
751
|
-
async function pruneWorktrees({ repoCommonDir = null } = {}) {
|
|
752
|
-
return maintenance.pruneWorktrees({ ...(await maintenanceScope()), repoCommonDir });
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/** After `sessionId` was deleted: autoCleanup removes its clean, merged worktree. */
|
|
756
|
-
async function cleanupDeletedSession(sessionId) {
|
|
757
|
-
// Its snapshots go with it below; no turn cleanup keeps retrying them.
|
|
758
|
-
cancelSnapshotCleanups(sessionId);
|
|
759
|
-
return maintenance.cleanupDeletedSessionWorktree(sessionId, {
|
|
760
|
-
manager: await backend(),
|
|
761
|
-
autoCleanup: worktreeSettings().autoCleanup,
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* Turn admission (see admission-gate admitTurn); resolves the release. The
|
|
767
|
-
* release also starts the removal of the read-only snapshots this turn
|
|
768
|
-
* made — only those (removeTurnSnapshots).
|
|
769
|
-
*/
|
|
770
|
-
async function admitTurnWorkspace({ signal } = {}) {
|
|
771
|
-
const release = await admitTurn({ sessionId: getSession()?.id || '', cwd: () => getCurrentCwd() || '', signal });
|
|
772
|
-
const made = new Set();
|
|
773
|
-
turnSnapshots = made;
|
|
774
|
-
return () => {
|
|
775
|
-
release();
|
|
776
|
-
if (turnSnapshots === made) turnSnapshots = null;
|
|
777
|
-
const sessionId = getSession()?.id;
|
|
778
|
-
if (made.size && sessionId) void removeTurnSnapshots(sessionId, [...made]);
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Remove a settled turn's snapshots (by creation `txIds`) once nothing runs
|
|
784
|
-
* in them: a worker turn or background shell holding a workspace lease
|
|
785
|
-
* inside one keeps it (manager removeSessionSnapshots reports it `in-use`).
|
|
786
|
-
* Those are retried with a doubling delay (`snapshotRetryMs`, at most
|
|
787
|
-
* 2^SNAPSHOT_BACKOFF_STEPS times it) for at most `snapshotRetryLimit`
|
|
788
|
-
* attempts; a snapshot removal that fails otherwise is not retried. What is
|
|
789
|
-
* left then — or when the runtime is disposed, the session closes, is
|
|
790
|
-
* deleted or is no longer the one hosted here — is the prune's and the
|
|
791
|
-
* startup reconcile's. Admission is not held meanwhile: the removal only
|
|
792
|
-
* ever names this turn's snapshots.
|
|
793
|
-
*/
|
|
794
|
-
async function removeTurnSnapshots(sessionId, txIds) {
|
|
795
|
-
if (disposed) return;
|
|
796
|
-
const controller = new AbortController();
|
|
797
|
-
snapshotCleanups.set(controller, sessionId);
|
|
798
|
-
try {
|
|
799
|
-
let pending = txIds;
|
|
800
|
-
for (let attempt = 0; pending.length && attempt < snapshotRetryLimit; attempt++) {
|
|
801
|
-
if (attempt > 0) {
|
|
802
|
-
const wait = snapshotRetryMs * 2 ** Math.min(attempt - 1, SNAPSHOT_BACKOFF_STEPS);
|
|
803
|
-
await delay(wait, undefined, { ref: false, signal: controller.signal });
|
|
804
|
-
}
|
|
805
|
-
const session = getSession();
|
|
806
|
-
if (session?.id !== sessionId || session.closed === true) return;
|
|
807
|
-
const report = await (await backend()).removeSessionSnapshots(sessionId, { txIds: pending });
|
|
808
|
-
pending = report.kept.filter((row) => row.reason === 'in-use').map((row) => row.txId);
|
|
809
|
-
}
|
|
810
|
-
} catch {
|
|
811
|
-
// Cancelled, or the backend failed: the rest is the maintenance's.
|
|
812
|
-
} finally {
|
|
813
|
-
snapshotCleanups.delete(controller);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
/** Stop the snapshot cleanups of `sessionId` (every session's when null). */
|
|
818
|
-
function cancelSnapshotCleanups(sessionId = null) {
|
|
819
|
-
for (const [controller, owner] of snapshotCleanups) {
|
|
820
|
-
if (sessionId === null || owner === sessionId) controller.abort();
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
/** Runtime teardown: no snapshot cleanup runs on (prune / startup reconcile finish them). */
|
|
825
|
-
function dispose() {
|
|
826
|
-
disposed = true;
|
|
827
|
-
cancelSnapshotCleanups();
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
/**
|
|
831
|
-
* Run in `branch`'s checkout (see enterProjectBranch). `ownTurn`
|
|
832
|
-
* (internal: the Lead's own tool call) tolerates the calling turn.
|
|
833
|
-
*/
|
|
834
|
-
async function selectBranch(
|
|
835
|
-
{ branch, create = false, expectedRevision, confirmLarge = false, txId = '' } = {},
|
|
836
|
-
{ ownTurn = false } = {}
|
|
837
|
-
) {
|
|
838
|
-
const session = await ensureSession();
|
|
839
|
-
const current = currentWorkspace(session);
|
|
840
|
-
if (current.branch === branch && current.worktreeId) {
|
|
841
|
-
assertRevision(current.revision, validRevision(expectedRevision));
|
|
842
|
-
pendingIntent = null;
|
|
843
|
-
return { revision: current.revision, workspace: current };
|
|
844
|
-
}
|
|
845
|
-
const target = { kind: 'branch', branch, create: create === true };
|
|
846
|
-
const result = await runTransition(
|
|
847
|
-
{ sessionId: session.id },
|
|
848
|
-
target,
|
|
849
|
-
{ expectedRevision, confirmLarge },
|
|
850
|
-
{ ownTurn, txId: String(txId || '').trim() }
|
|
851
|
-
);
|
|
852
|
-
pendingIntent = null;
|
|
853
|
-
return result;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/** WORKTREE_PROJECT_MISMATCH unless `repoCommonDir` (when given) is the session Project's repository. */
|
|
857
|
-
async function sessionProject(session, repoCommonDir) {
|
|
858
|
-
const workspace = currentWorkspace(session);
|
|
859
|
-
const project = await inspectProject(workspace.projectPath);
|
|
860
|
-
if (repoCommonDir && !samePath(repoCommonDir, project.repoCommonDir)) {
|
|
861
|
-
throw worktreeError(
|
|
862
|
-
'WORKTREE_PROJECT_MISMATCH',
|
|
863
|
-
"Managed worktrees belong to this session's Project repository; run the command there.",
|
|
864
|
-
{ projectPath: workspace.projectPath, repoCommonDir }
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
return { workspace, project };
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* A managed worktree for `branch` without moving the session (`git
|
|
872
|
-
* worktree add`): retained by this session like a branch it selected, so
|
|
873
|
-
* entering it later (selectBranch) reuses it. A branch already checked out
|
|
874
|
-
* elsewhere is WORKTREE_BRANCH_OCCUPIED, except in this session's own
|
|
875
|
-
* managed worktree, which is returned as it is. `startPoint` (with
|
|
876
|
-
* `create`): the commit the new branch starts at.
|
|
877
|
-
*/
|
|
878
|
-
async function addBranchWorktree({
|
|
879
|
-
branch,
|
|
880
|
-
create = false,
|
|
881
|
-
startPoint = null,
|
|
882
|
-
confirmLarge = false,
|
|
883
|
-
txId = '',
|
|
884
|
-
repoCommonDir = null,
|
|
885
|
-
} = {}) {
|
|
886
|
-
const session = hostedSession(null);
|
|
887
|
-
const { workspace } = await sessionProject(session, repoCommonDir);
|
|
888
|
-
const backendApi = await backend();
|
|
889
|
-
const { occupancy } = await backendApi.listBranchOccupancy({
|
|
890
|
-
projectPath: workspace.projectPath,
|
|
891
|
-
sessionId: session.id,
|
|
892
|
-
});
|
|
893
|
-
const held = occupancy.find((row) => row.branch === branch && row.kind !== 'free');
|
|
894
|
-
if (held && !(held.kind === 'session' && held.sessionId === session.id)) {
|
|
895
|
-
throw worktreeError('WORKTREE_BRANCH_OCCUPIED', `Branch ${branch} is checked out in ${held.path}.`, {
|
|
896
|
-
branch,
|
|
897
|
-
path: held.path,
|
|
898
|
-
kind: held.kind,
|
|
899
|
-
...(held.sessionId ? { sessionId: held.sessionId } : {}),
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
const resolved = await backendApi.resolveBranchWorkspace({
|
|
903
|
-
projectPath: workspace.projectPath,
|
|
904
|
-
branch,
|
|
905
|
-
create: create === true,
|
|
906
|
-
startPoint,
|
|
907
|
-
sessionId: session.id,
|
|
908
|
-
runtimeToken,
|
|
909
|
-
txId: String(txId || '').trim() || newTxId(),
|
|
910
|
-
confirmLarge: confirmLarge === true,
|
|
911
|
-
});
|
|
912
|
-
return {
|
|
913
|
-
path: resolved.path,
|
|
914
|
-
branch,
|
|
915
|
-
worktreeId: resolved.entry?.id ?? null,
|
|
916
|
-
created: resolved.created === true,
|
|
917
|
-
baseCommit: resolved.entry?.baseCommit ?? null,
|
|
918
|
-
mergeTarget: resolved.entry?.mergeTarget ?? null,
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
/**
|
|
923
|
-
* A read-only snapshot of `commit` for this session's turn (`git worktree
|
|
924
|
-
* add --detach`): a detached managed checkout no session works in,
|
|
925
|
-
* removed when the turn settles (and nothing runs in it any more). The
|
|
926
|
-
* session does not move. Only inside an admitted turn: a snapshot always
|
|
927
|
-
* belongs to the turn that made it.
|
|
928
|
-
*/
|
|
929
|
-
async function createSnapshot({ commit = 'HEAD', confirmLarge = false, txId = '', repoCommonDir = null } = {}) {
|
|
930
|
-
const session = hostedSession(null);
|
|
931
|
-
const made = turnSnapshots;
|
|
932
|
-
if (!made) throw new Error('A read-only snapshot belongs to a turn; none is running in this session.');
|
|
933
|
-
const { workspace } = await sessionProject(session, repoCommonDir);
|
|
934
|
-
const tx = String(txId || '').trim() || newTxId();
|
|
935
|
-
const entry = await (await backend()).createSnapshotWorktree({
|
|
936
|
-
projectPath: workspace.projectPath,
|
|
937
|
-
sessionId: session.id,
|
|
938
|
-
runtimeToken,
|
|
939
|
-
commit,
|
|
940
|
-
txId: tx,
|
|
941
|
-
confirmLarge: confirmLarge === true,
|
|
942
|
-
});
|
|
943
|
-
made.add(tx);
|
|
944
|
-
return entry;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
/**
|
|
948
|
-
* Remove this session's snapshot at `path` now; a dirty one only with
|
|
949
|
-
* `force`, and none while a worker or background shell runs in it.
|
|
950
|
-
*/
|
|
951
|
-
async function removeSnapshot({ path, force = false } = {}) {
|
|
952
|
-
const session = hostedSession(null);
|
|
953
|
-
const backendApi = await backend();
|
|
954
|
-
const entry = await backendApi.getWorktreeByPath(path);
|
|
955
|
-
if (entry?.kind !== WORKTREE_SNAPSHOT_KIND) throw worktreeError('WORKTREE_MISSING', undefined, { path });
|
|
956
|
-
if (entry.holderSessionId !== session.id) {
|
|
957
|
-
throw worktreeError(
|
|
958
|
-
'WORKTREE_BUSY_JOBS',
|
|
959
|
-
"This snapshot belongs to another session's turn; it is removed when that turn ends, or by a prune.",
|
|
960
|
-
{ reason: 'snapshot-held', sessionId: entry.holderSessionId, entryId: entry.id }
|
|
961
|
-
);
|
|
962
|
-
}
|
|
963
|
-
const removed = await backendApi.removeSnapshotWorktree({ entryId: entry.id, force: force === true });
|
|
964
|
-
return { ...removed, entryId: entry.id, path: entry.path };
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* "워크트리": re-enter the retained worktree now, or arm the lazy creation
|
|
969
|
-
* for the next message (confirmLarge acknowledges WORKTREE_LARGE_REPO_CONFIRM).
|
|
970
|
-
*
|
|
971
|
-
* `provision` (a New Task's first send, before its prompt is submitted)
|
|
972
|
-
* creates and enters the worktree right away instead: `prompt` names it
|
|
973
|
-
* like the lazy path would and `txId` (the submission id) makes a resend
|
|
974
|
-
* reuse it. Typed failures (large repository, disk, busy) therefore reach
|
|
975
|
-
* the sender while its draft is still unsent, and the turn that follows
|
|
976
|
-
* finds the session already bound.
|
|
977
|
-
*
|
|
978
|
-
* A provisioning that creates a new worktree decides the project folder's
|
|
979
|
-
* uncommitted changes (tracked and untracked): none → it proceeds; without
|
|
980
|
-
* `carryChanges` it refuses with WORKTREE_DIRTY_CHOICE (`details.count`);
|
|
981
|
-
* `leave` starts the worktree at HEAD and leaves them; `carry` moves them
|
|
982
|
-
* into it (the result's `changes` reports which). Re-entering a retained
|
|
983
|
-
* worktree never touches them.
|
|
984
|
-
*
|
|
985
|
-
* `ownTurn` (internal: the Lead's own tool call) tolerates the calling turn.
|
|
986
|
-
*/
|
|
987
|
-
async function requestWorktree(
|
|
988
|
-
{
|
|
989
|
-
name = null,
|
|
990
|
-
confirmLarge = false,
|
|
991
|
-
provision = false,
|
|
992
|
-
carryChanges,
|
|
993
|
-
prompt = '',
|
|
994
|
-
txId = '',
|
|
995
|
-
expectedRevision,
|
|
996
|
-
} = {},
|
|
997
|
-
{ ownTurn = false } = {}
|
|
998
|
-
) {
|
|
999
|
-
const nameHint = validName(name);
|
|
1000
|
-
const expected = validRevision(expectedRevision);
|
|
1001
|
-
if (carryChanges !== undefined && carryChanges !== null && !WORKTREE_CARRY_CHOICES.includes(carryChanges)) {
|
|
1002
|
-
throw new TypeError(`carryChanges must be one of ${WORKTREE_CARRY_CHOICES.join(', ')}.`);
|
|
1003
|
-
}
|
|
1004
|
-
const session = provision === true ? await ensureSession() : getSession();
|
|
1005
|
-
// No session yet: the menu it was read from had revision 0.
|
|
1006
|
-
const current = session?.id ? currentWorkspace(session) : null;
|
|
1007
|
-
assertRevision(current?.revision ?? 0, expected);
|
|
1008
|
-
if (current) {
|
|
1009
|
-
if (isWorktreeBound(current)) return { revision: current.revision, workspace: current, pending: false };
|
|
1010
|
-
if (await (await backend()).getSessionWorktree(session.id)) {
|
|
1011
|
-
const entered = await runTransition(
|
|
1012
|
-
{ sessionId: session.id },
|
|
1013
|
-
{ kind: 'worktree' },
|
|
1014
|
-
{ expectedRevision: expected },
|
|
1015
|
-
{ ownTurn }
|
|
1016
|
-
);
|
|
1017
|
-
return { ...entered, pending: false };
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
const priorIntent = pendingIntent;
|
|
1021
|
-
pendingIntent = {
|
|
1022
|
-
name: nameHint ?? pendingIntent?.name ?? null,
|
|
1023
|
-
confirmLarge: confirmLarge === true || pendingIntent?.confirmLarge === true,
|
|
1024
|
-
explicit: true,
|
|
1025
|
-
sessionId: session?.id || getReservedSessionId?.() || null,
|
|
1026
|
-
txId: pendingIntent?.txId ?? null,
|
|
1027
|
-
};
|
|
1028
|
-
if (provision === true) {
|
|
1029
|
-
const intent = pendingIntent;
|
|
1030
|
-
try {
|
|
1031
|
-
return {
|
|
1032
|
-
...(await provisionIntent(session, intent, txId, prompt, {
|
|
1033
|
-
expectedRevision: expected,
|
|
1034
|
-
changes: carryChanges ?? 'ask',
|
|
1035
|
-
ownTurn: ownTurn === true,
|
|
1036
|
-
})),
|
|
1037
|
-
pending: false,
|
|
1038
|
-
};
|
|
1039
|
-
} catch (error) {
|
|
1040
|
-
// A refused move (uncommitted changes to decide, busy, …) is never
|
|
1041
|
-
// re-armed for the next turn, where nobody could answer it.
|
|
1042
|
-
if (pendingIntent === intent) pendingIntent = priorIntent;
|
|
1043
|
-
throw error;
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
const workspace = session?.id ? currentWorkspace(session) : null;
|
|
1047
|
-
return { revision: workspace?.revision ?? 0, workspace, pending: true };
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
/**
|
|
1051
|
-
* Merge the retained worktree into the project folder's checked-out branch.
|
|
1052
|
-
* `ownTurn` (internal: the Lead's own tool call) tolerates the calling turn.
|
|
1053
|
-
*/
|
|
1054
|
-
async function mergeWorkspace(
|
|
1055
|
-
{ confirmed = false, target = null, cleanup = false, expectedRevision } = {},
|
|
1056
|
-
{ ownTurn = false } = {}
|
|
1057
|
-
) {
|
|
1058
|
-
if (confirmed !== true) {
|
|
1059
|
-
throw worktreeError('WORKTREE_CONFIRM_REQUIRED', 'mergeWorkspace requires confirmed: true.');
|
|
1060
|
-
}
|
|
1061
|
-
const expected = validRevision(expectedRevision);
|
|
1062
|
-
const session = hostedSession(null);
|
|
1063
|
-
const merged = await mergeRetainedWorktree(session, { target, ownTurn, expectedRevision: expected });
|
|
1064
|
-
if (cleanup !== true || merged.merged !== true) return merged;
|
|
1065
|
-
// "Merge and clean up": with autoCleanup on, the merged worktree goes now
|
|
1066
|
-
// (a dirty one stays, reported); off, nothing is removed automatically.
|
|
1067
|
-
// The merge is committed: a failed or refused cleanup (dirty, retained
|
|
1068
|
-
// file recovery data, ...) is a cleanup notice, never a merge failure.
|
|
1069
|
-
if (worktreeSettings().autoCleanup !== true) {
|
|
1070
|
-
return {
|
|
1071
|
-
...merged,
|
|
1072
|
-
cleanup: { removed: false, reason: 'auto-cleanup-off', message: 'Auto cleanup is off; the worktree is kept.' },
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
// The cleanup never moves a session: while this session still works in the
|
|
1076
|
-
// merged folder (or any other session uses it) the removal is refused.
|
|
1077
|
-
try {
|
|
1078
|
-
if (isWorktreeBound(currentWorkspace(session))) {
|
|
1079
|
-
throw worktreeRefusal('folder-in-use', { sessions: [session.id] });
|
|
1080
|
-
}
|
|
1081
|
-
const entry = await (await backend()).getSessionWorktree(session.id);
|
|
1082
|
-
const removed = await withManagementFolders(
|
|
1083
|
-
entry ? [entry.path] : [],
|
|
1084
|
-
true,
|
|
1085
|
-
() => removeWorktree({ force: false }, { ownTurn }),
|
|
1086
|
-
{ actor: session.id }
|
|
1087
|
-
);
|
|
1088
|
-
return { ...merged, cleanup: removed.removed, revision: removed.revision, workspace: removed.workspace };
|
|
1089
|
-
} catch (error) {
|
|
1090
|
-
// The merge stands: a refused or incomplete cleanup (in use, dirty,
|
|
1091
|
-
// unmerged, retained file recovery data, partly removed, …) is
|
|
1092
|
-
// reported, never thrown, with the workspace as it is now.
|
|
1093
|
-
const workspace = currentWorkspace(session);
|
|
1094
|
-
const code = typeof error?.code === 'string' ? error.code : '';
|
|
1095
|
-
return {
|
|
1096
|
-
...merged,
|
|
1097
|
-
cleanup: {
|
|
1098
|
-
removed: false,
|
|
1099
|
-
reason: code || 'WORKTREE_CLEANUP_FAILED',
|
|
1100
|
-
...(code ? { code } : {}),
|
|
1101
|
-
message: String(error?.message || error),
|
|
1102
|
-
},
|
|
1103
|
-
revision: workspace.revision,
|
|
1104
|
-
workspace,
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
/**
|
|
1110
|
-
* The merge itself. The retained worktree must belong to this session's
|
|
1111
|
-
* Project (same repository and Project folder — a Project switch may have
|
|
1112
|
-
* left it behind); its branch, or the branch of the linked checkout the
|
|
1113
|
-
* session works in, is merged into the project folder through the shared
|
|
1114
|
-
* guarded path (mergeConfirmedTarget): both folders validated and held.
|
|
1115
|
-
*/
|
|
1116
|
-
async function mergeRetainedWorktree(session, { target, ownTurn, expectedRevision }) {
|
|
1117
|
-
const workspace = currentWorkspace(session);
|
|
1118
|
-
assertRevision(workspace.revision, expectedRevision);
|
|
1119
|
-
const leave = beginWorkspaceTransition(session.id, `merge:${newTxId()}`);
|
|
1120
|
-
try {
|
|
1121
|
-
await assertLeadIdle(session, { ownTurn: ownTurn === true });
|
|
1122
|
-
const project = await inspectProject(workspace.projectPath);
|
|
1123
|
-
let branch;
|
|
1124
|
-
if (!workspace.worktreeId && !samePath(workspace.cwd, workspace.projectPath)) {
|
|
1125
|
-
const source = await inspectProject(workspace.cwd);
|
|
1126
|
-
if (!samePath(source.repoCommonDir, project.repoCommonDir)) throw worktreeError('WORKTREE_PROJECT_MISMATCH');
|
|
1127
|
-
branch = source.currentBranch;
|
|
1128
|
-
} else {
|
|
1129
|
-
const backendApi = await backend();
|
|
1130
|
-
const entry = workspace.worktreeId
|
|
1131
|
-
? await backendApi.findWorktreeContaining(workspace.cwd)
|
|
1132
|
-
: await backendApi.getSessionWorktree(session.id);
|
|
1133
|
-
if (!entry) throw worktreeError('WORKTREE_MISSING', 'This session has no worktree to merge.');
|
|
1134
|
-
assertRetainedFits(entry, project, workspace.projectPath);
|
|
1135
|
-
if (!samePath(entry.projectPath, workspace.projectPath)) {
|
|
1136
|
-
throw worktreeError('WORKTREE_PROJECT_MISMATCH', undefined, {
|
|
1137
|
-
worktreeId: entry.id,
|
|
1138
|
-
worktreeProject: entry.projectPath,
|
|
1139
|
-
projectPath: workspace.projectPath,
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
branch = entry.branch;
|
|
1143
|
-
}
|
|
1144
|
-
return await mergeConfirmedTarget(
|
|
1145
|
-
{ projectPath: workspace.projectPath, branch, target },
|
|
1146
|
-
(folders, removing, run) => withManagementFolders(folders, removing, run, { actor: session.id })
|
|
1147
|
-
);
|
|
1148
|
-
} finally {
|
|
1149
|
-
leave();
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
/**
|
|
1154
|
-
* Move the hosted Lead to another Project folder (session set-project, the
|
|
1155
|
-
* desktop/remote setCwd): the one authority for a Project switch, admitted
|
|
1156
|
-
* like a workspace transition. Refused while a turn runs (`ownTurn`: the
|
|
1157
|
-
* Lead's own tool call tolerates its calling turn), while workers run, and
|
|
1158
|
-
* while the session works in a managed worktree. Serialized with turns and
|
|
1159
|
-
* other workspace changes; the prompt refresh of the Lead and its idle
|
|
1160
|
-
* workers completes (or rolls the switch back) before it resolves. Without
|
|
1161
|
-
* a session (a draft) only the runtime cwd and the selection move.
|
|
1162
|
-
* `classifyByMembership` (the desktop/remote move): a folder outside every
|
|
1163
|
-
* registered Project is classified as a task (cwd-apply).
|
|
1164
|
-
* @returns {Promise<string>} the runtime cwd after the switch
|
|
1165
|
-
*/
|
|
1166
|
-
async function switchProject(cwd, { ownTurn = false, classifyByMembership = false } = {}) {
|
|
1167
|
-
const selection = { classifyByMembership: classifyByMembership === true };
|
|
1168
|
-
const session = getSession();
|
|
1169
|
-
if (!session?.id) {
|
|
1170
|
-
applyResolvedCwd(cwd, { persistProjectSelection: true, ...selection });
|
|
1171
|
-
return getCurrentCwd();
|
|
1172
|
-
}
|
|
1173
|
-
const leave = beginWorkspaceTransition(session.id, `project:${newTxId()}`);
|
|
1174
|
-
const journal = [];
|
|
1175
|
-
try {
|
|
1176
|
-
await assertLeadIdle(session, { ownTurn: ownTurn === true, shellJobs: false });
|
|
1177
|
-
if (isWorktreeBound(readSessionWorkspace(session))) throw new Error(WORKTREE_BOUND_SWITCH);
|
|
1178
|
-
await steps.switchProject(session, cwd, journal, selection);
|
|
1179
|
-
return getCurrentCwd();
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
await undoAll(journal, error);
|
|
1182
|
-
throw error;
|
|
1183
|
-
} finally {
|
|
1184
|
-
leave();
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
async function removeWorktree({ force = false, expectedRevision } = {}, { ownTurn = false } = {}) {
|
|
1189
|
-
const expected = validRevision(expectedRevision);
|
|
1190
|
-
const session = hostedSession(null);
|
|
1191
|
-
return transition(
|
|
1192
|
-
session,
|
|
1193
|
-
{ kind: 'remove-worktree', force: force === true },
|
|
1194
|
-
{ ownTurn: ownTurn === true, expectedRevision: expected }
|
|
1195
|
-
);
|
|
261
|
+
return runner.runTransition(sessionRef, target, options);
|
|
1196
262
|
}
|
|
1197
263
|
|
|
1198
264
|
return {
|
|
1199
265
|
applyWorkspaceTransition,
|
|
1200
|
-
prepareTurnWorkspace,
|
|
1201
|
-
admitTurnWorkspace,
|
|
266
|
+
prepareTurnWorkspace: intents.prepareTurnWorkspace,
|
|
267
|
+
admitTurnWorkspace: checkouts.admitTurnWorkspace,
|
|
1202
268
|
/** The durable workspace record of the hosted session (null: project folder, never moved). */
|
|
1203
269
|
getWorkspace: () => getSession()?.workspace ?? null,
|
|
1204
270
|
/** What the session snapshot publishes (see getLiveWorkspace). */
|
|
1205
|
-
getLiveWorkspace,
|
|
1206
|
-
getBranchMenu,
|
|
271
|
+
getLiveWorkspace: view.getLiveWorkspace,
|
|
272
|
+
getBranchMenu: view.getBranchMenu,
|
|
1207
273
|
getProjectBranchMenu: projectBranches.getProjectBranchMenu,
|
|
1208
|
-
selectBranch,
|
|
274
|
+
selectBranch: intents.selectBranch,
|
|
1209
275
|
selectProjectBranch: projectBranches.selectProjectBranch,
|
|
1210
276
|
mergeProjectBranch: projectBranches.mergeProjectBranch,
|
|
1211
277
|
inspectProjectBranch: projectBranches.inspectProjectBranch,
|
|
1212
278
|
manageProjectBranch: projectBranches.manageProjectBranch,
|
|
1213
|
-
requestWorktree,
|
|
1214
|
-
mergeWorkspace,
|
|
1215
|
-
removeWorktree,
|
|
1216
|
-
addBranchWorktree,
|
|
1217
|
-
createSnapshot,
|
|
1218
|
-
removeSnapshot,
|
|
1219
|
-
switchProject,
|
|
1220
|
-
listWorktrees,
|
|
1221
|
-
pruneWorktrees,
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
279
|
+
requestWorktree: intents.requestWorktree,
|
|
280
|
+
mergeWorkspace: merge.mergeWorkspace,
|
|
281
|
+
removeWorktree: runner.removeWorktree,
|
|
282
|
+
addBranchWorktree: checkouts.addBranchWorktree,
|
|
283
|
+
createSnapshot: checkouts.createSnapshot,
|
|
284
|
+
removeSnapshot: checkouts.removeSnapshot,
|
|
285
|
+
switchProject: runner.switchProject,
|
|
286
|
+
listWorktrees: worktrees.listWorktrees,
|
|
287
|
+
pruneWorktrees: worktrees.pruneWorktrees,
|
|
288
|
+
removeOrphanWorktree: worktrees.removeOrphanWorktree,
|
|
289
|
+
cleanupDeletedSession: worktrees.cleanupDeletedSession,
|
|
290
|
+
onWorktreeSetup: setup.onWorktreeSetup,
|
|
291
|
+
dispose: checkouts.dispose,
|
|
1225
292
|
};
|
|
1226
293
|
}
|