mixdog 1.2.2 → 1.2.4
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/README.md +18 -0
- package/package.json +2 -1
- package/scripts/gate-local.mjs +6 -4
- package/scripts/lib/gate-scheduler.mjs +29 -11
- package/scripts/probe-provider-catalog.mjs +31 -0
- package/scripts/provider-pingpong-live.mjs +599 -0
- package/scripts/session-sweep.mjs +5 -25
- package/src/agents/advisor/AGENT.md +1 -1
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/maintainer/AGENT.md +1 -1
- package/src/agents/maintainer/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -4
- package/src/agents/security/AGENT.md +1 -2
- package/src/agents/writer/AGENT.md +2 -3
- package/src/defaults/shipped-history.json +11 -1
- package/src/defaults/skills/image/SKILL.md +21 -23
- package/src/defaults/skills/setup/SKILL.md +6 -1
- package/src/defaults/skills/setup/references/actions.md +44 -22
- package/src/defaults/skills/setup/references/surfaces.md +10 -8
- package/src/headless-exec.mjs +15 -8
- package/src/lib/keychain-cjs.cjs +20 -15
- package/src/output-styles/common.md +11 -10
- package/src/rules/agent/AGENT.md +6 -4
- package/src/rules/lead/LEAD.md +7 -9
- package/src/rules/shared/45-destructive.md +4 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
- package/src/runtime/agent/orchestrator/config.mjs +5 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +9 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +31 -15
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +131 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +125 -8
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +12 -62
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +117 -41
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
- package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +39 -5
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +42 -11
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +109 -6
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +17 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +73 -7
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +75 -6
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
- package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +29 -5
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +36 -13
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +101 -11
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +19 -14
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +371 -41
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +97 -49
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +81 -10
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +35 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +253 -42
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
- package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +392 -0
- package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
- package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +50 -22
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +133 -21
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +92 -16
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -13
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +133 -62
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +84 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +155 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +40 -24
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +183 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +87 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -2
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +28 -5
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +72 -24
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +50 -16
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -19
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -36
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -9
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +181 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +78 -70
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +74 -0
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +150 -10
- package/src/runtime/agent/orchestrator/providers/registry.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
- package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +164 -0
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +522 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +195 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +104 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +24 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
- package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +16 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +178 -71
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +102 -12
- package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -11
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +16 -93
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +148 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +89 -1
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -27
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +24 -10
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +135 -0
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +10 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +29 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +147 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +23 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +54 -13
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +22 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
- package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +231 -16
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +47 -17
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +26 -4
- package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +36 -35
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +4 -10
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/side-question.mjs +197 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +22 -15
- package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
- package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +44 -37
- package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -21
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +136 -196
- package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +53 -115
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +17 -10
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
- package/src/runtime/agent/orchestrator/session/store.mjs +22 -19
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +12 -12
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +38 -14
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +79 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +218 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
- package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
- package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +247 -49
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
- package/src/runtime/attachments/store.d.mts +9 -0
- package/src/runtime/attachments/store.mjs +80 -9
- package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
- package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
- package/src/runtime/computer-bridge/action-schema.mjs +219 -196
- package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
- package/src/runtime/effort-judge/judge-client.mjs +21 -4
- package/src/runtime/effort-judge/judge-worker.mjs +4 -1
- package/src/runtime/github/tool.mjs +208 -17
- package/src/runtime/media/adapters/codex-image.mjs +140 -21
- package/src/runtime/media/catalog.mjs +4 -4
- package/src/runtime/media/chatgpt-usage.mjs +55 -0
- package/src/runtime/media/defaults.mjs +37 -0
- package/src/runtime/media/jobs.mjs +12 -2
- package/src/runtime/media/lanes.mjs +12 -0
- package/src/runtime/media/tool-defs.mjs +15 -4
- package/src/runtime/media/tool.mjs +41 -12
- package/src/runtime/media/upstream-error.mjs +72 -6
- package/src/runtime/memory/index.mjs +1 -0
- package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
- package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
- package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
- package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
- package/src/runtime/memory/lib/pg/process.mjs +36 -33
- package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +112 -4
- package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
- package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/html-browser.mjs +18 -8
- package/src/runtime/office/authoring/html-document-author.mjs +2 -0
- package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-measure.mjs +3 -1
- package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +3 -1
- package/src/runtime/office/core/office-actions-read.mjs +3 -0
- package/src/runtime/office/core/office-render-preview.mjs +2 -0
- package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
- package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
- package/src/runtime/office/core/office-sessions.mjs +5 -0
- package/src/runtime/office/core/office-transactions.mjs +3 -0
- package/src/runtime/office/core/office-write-ledger.mjs +94 -0
- package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
- package/src/runtime/office/index.mjs +29 -0
- package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
- package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
- package/src/runtime/office/pdf/pdf-render.mjs +23 -7
- package/src/runtime/office/pdf/pdf-security.mjs +2 -0
- package/src/runtime/office/portable/portable-opc.mjs +4 -1
- package/src/runtime/office/quality/visual-diff.mjs +2 -0
- package/src/runtime/session-search/session-search-ingest.mjs +1 -1
- package/src/runtime/shared/agent-reap-state.mjs +13 -0
- package/src/runtime/shared/atomic-file.d.mts +1 -1
- package/src/runtime/shared/background-tasks.mjs +6 -1
- package/src/runtime/shared/bounded-file-read.mjs +13 -0
- package/src/runtime/shared/bounded-line-diff.mjs +191 -0
- package/src/runtime/shared/browser-launch.mjs +15 -0
- package/src/runtime/shared/buffered-appender.mjs +18 -0
- package/src/runtime/shared/config.mjs +78 -18
- package/src/runtime/shared/err-text.mjs +48 -4
- package/src/runtime/shared/file-lock.mjs +6 -5
- package/src/runtime/shared/file-permissions.d.mts +6 -1
- package/src/runtime/shared/file-permissions.mjs +54 -6
- package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
- package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
- package/src/runtime/shared/loopback-listener.mjs +9 -0
- package/src/runtime/shared/model-route-chain.d.mts +8 -0
- package/src/runtime/shared/model-route-chain.mjs +48 -0
- package/src/runtime/shared/model-route-fallback.mjs +22 -0
- package/src/runtime/shared/pid-liveness.mjs +12 -0
- package/src/runtime/shared/provider-api-key.mjs +8 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
- package/src/runtime/shared/quiet-progress.d.mts +35 -0
- package/src/runtime/shared/quiet-progress.mjs +101 -0
- package/src/runtime/shared/quota-resume-store.mjs +32 -1
- package/src/runtime/shared/read-row-numbers.mjs +44 -0
- package/src/runtime/shared/remote-intent.mjs +2 -42
- package/src/runtime/shared/review-diff.mjs +52 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
- package/src/runtime/shared/session-sidebar-host.mjs +38 -0
- package/src/runtime/shared/slow-span.mjs +121 -0
- package/src/runtime/shared/statusline/statusline-segments.mjs +52 -6
- package/src/runtime/shared/tool-card-model/card-header.mjs +7 -1
- package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
- package/src/runtime/shared/tool-card-model.mjs +5 -2
- package/src/runtime/shared/tool-output-budget.mjs +2 -0
- package/src/runtime/shared/tool-output-limit.mjs +4 -0
- package/src/runtime/shared/tool-primitives.mjs +13 -0
- package/src/runtime/shared/tool-result-summary.mjs +22 -1
- package/src/runtime/shared/tool-status.mjs +3 -2
- package/src/runtime/shared/tool-surface.mjs +20 -6
- package/src/runtime/shared/tool-work-units.mjs +54 -11
- package/src/runtime/shared/transcript-metadata.mjs +49 -1
- package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
- package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
- package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
- package/src/runtime/shared/transcript-writer.mjs +24 -6
- package/src/runtime/shared/turn-changes.mjs +269 -100
- package/src/runtime/shared/turn-review-reply.mjs +8 -39
- package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
- package/src/runtime/shared/turn-snapshot.mjs +865 -683
- package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
- package/src/runtime/shared/user-data-guard.mjs +262 -49
- package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
- package/src/runtime/shared/worktrees/carry.mjs +533 -0
- package/src/runtime/shared/worktrees/contract.mjs +60 -0
- package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
- package/src/runtime/shared/worktrees/file-recovery.mjs +84 -0
- package/src/runtime/shared/worktrees/git-head.mjs +214 -0
- package/src/runtime/shared/worktrees/git.mjs +8 -3
- package/src/runtime/shared/worktrees/include.mjs +125 -7
- package/src/runtime/shared/worktrees/lock.mjs +1 -1
- package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
- package/src/runtime/shared/worktrees/manager.mjs +525 -31
- package/src/runtime/shared/worktrees/merge.mjs +31 -21
- package/src/runtime/shared/worktrees/owner.mjs +2 -1
- package/src/runtime/shared/worktrees/paths.d.mts +6 -0
- package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
- package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
- package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
- package/src/runtime/terminal-bridge/client.mjs +57 -20
- package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
- package/src/runtime/tidy/apply.mjs +47 -2
- package/src/runtime/tidy/run-engines.mjs +11 -5
- package/src/session-runtime/agent-tool-routing.mjs +19 -13
- package/src/session-runtime/boot/apis.mjs +20 -6
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +4 -0
- package/src/session-runtime/boot/infrastructure.mjs +6 -1
- package/src/session-runtime/boot/session.mjs +3 -3
- package/src/session-runtime/boot/tools.mjs +7 -1
- package/src/session-runtime/conversation-import/service.mjs +1 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
- package/src/session-runtime/goal-facade-api.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +91 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +15 -5
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +137 -26
- package/src/session-runtime/internal-tool-executor.mjs +8 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +187 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
- package/src/session-runtime/lifecycle/session-catalog.mjs +25 -18
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +36 -48
- package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
- package/src/session-runtime/lifecycle/shared.mjs +5 -6
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
- package/src/session-runtime/lifecycle/teardown.mjs +70 -14
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/mcp-glue/connect.mjs +17 -5
- package/src/session-runtime/media-api.mjs +8 -0
- package/src/session-runtime/model-route/route-ownership.mjs +60 -0
- package/src/session-runtime/model-route/route-persist.mjs +3 -2
- package/src/session-runtime/model-route/set-route.mjs +116 -36
- package/src/session-runtime/model-route/web-search-route.mjs +7 -2
- package/src/session-runtime/model-route-api.mjs +4 -2
- package/src/session-runtime/native-web-search/routes.mjs +37 -24
- package/src/session-runtime/native-web-search-usage.mjs +51 -0
- package/src/session-runtime/native-web-search.mjs +19 -9
- package/src/session-runtime/new-session-config.mjs +4 -2
- package/src/session-runtime/provider-auth/login.mjs +4 -0
- package/src/session-runtime/provider-auth/setup.mjs +4 -0
- package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
- package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
- package/src/session-runtime/provider-models.mjs +1 -0
- package/src/session-runtime/provider-usage.mjs +4 -0
- package/src/session-runtime/resource-scope-decorate.mjs +10 -3
- package/src/session-runtime/route-resolution.mjs +3 -1
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +6 -0
- package/src/session-runtime/runtime-review-api.mjs +8 -2
- package/src/session-runtime/services/agent-task-status.mjs +2 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
- package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
- package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
- package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
- package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
- package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
- package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
- package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +82 -13
- package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
- package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
- package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +18 -10
- package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
- package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
- package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
- package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
- package/src/session-runtime/services/agent-tool/worker-index.mjs +74 -15
- package/src/session-runtime/services/agent-tool.mjs +7 -0
- package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
- package/src/session-runtime/services/channel-admin.mjs +19 -3
- package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
- package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
- package/src/session-runtime/services/provider-admin.mjs +158 -15
- package/src/session-runtime/session-create/session-options.mjs +7 -5
- package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
- package/src/session-runtime/session-transcript-purge.mjs +32 -0
- package/src/session-runtime/session-transcript.mjs +4 -5
- package/src/session-runtime/session-turn-api.mjs +17 -14
- package/src/session-runtime/settings-builtin-tools-api.mjs +18 -6
- package/src/session-runtime/setup-tool/executor.mjs +57 -9
- package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +18 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +25 -4
- package/src/session-runtime/skills-api.mjs +3 -5
- package/src/session-runtime/tool-policy-refresh.mjs +83 -32
- package/src/session-runtime/tool-surface.mjs +5 -1
- package/src/session-runtime/turn/agent-ops.mjs +49 -2
- package/src/session-runtime/turn/compaction-gate.mjs +25 -0
- package/src/session-runtime/turn/session-ops.mjs +7 -5
- package/src/session-runtime/turn/side-question-ops.mjs +19 -0
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +7 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +12 -3
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +18 -2
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +26 -45
- package/src/session-runtime/usage-stats-api.mjs +46 -11
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
- package/src/session-runtime/workspace/admission-gate.mjs +44 -15
- package/src/session-runtime/workspace/project-branches.mjs +35 -41
- package/src/session-runtime/workspace/transition-steps.mjs +215 -116
- package/src/session-runtime/workspace/transitions.mjs +428 -73
- package/src/standalone/channel-transport.mjs +3 -4
- package/src/standalone/daemon-agent-control.mjs +9 -1
- package/src/standalone/daemon-desktop-runtime.mjs +32 -1
- package/src/standalone/daemon-local-session-bridge.mjs +14 -1
- package/src/standalone/daemon-owner.mjs +17 -4
- package/src/standalone/daemon-pg-stop.mjs +49 -0
- package/src/standalone/daemon-session-control.mjs +88 -0
- package/src/standalone/daemon-stop-budget.d.mts +2 -0
- package/src/standalone/daemon-stop-budget.mjs +18 -0
- package/src/standalone/daemon-stored-session-views.mjs +25 -3
- package/src/standalone/daemon-telemetry.mjs +19 -0
- package/src/standalone/daemon.mjs +63 -1
- package/src/standalone/desktop-service-registry.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +80 -30
- package/src/standalone/session-client.mjs +42 -0
- package/src/standalone/session-protocol.mjs +14 -0
- package/src/standalone/session-proxy/projection.mjs +41 -4
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
- package/src/standalone/session-runtime-inline-host.mjs +3 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
- package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
- package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
- package/src/standalone/session-service/agent-tree.mjs +28 -2
- package/src/standalone/session-service/entries/session-loads.mjs +16 -5
- package/src/standalone/session-service/entries.mjs +1 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
- package/src/standalone/session-service/quota-resume.mjs +28 -13
- package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
- package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
- package/src/standalone/session-service-api.mjs +6 -1
- package/src/standalone/session-state-patch.d.mts +2 -1
- package/src/standalone/session-state-patch.mjs +41 -1
- package/src/standalone/session-tool-output.mjs +82 -0
- package/src/tui/App.jsx +25 -0
- package/src/tui/app/app-view/floating-panel.jsx +26 -1
- package/src/tui/app/app-view.jsx +2 -1
- package/src/tui/app/btw-controller.mjs +118 -0
- package/src/tui/app/create-app-pickers.mjs +2 -0
- package/src/tui/app/provider-setup/provider-items.mjs +8 -0
- package/src/tui/app/provider-setup-picker.mjs +15 -3
- package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
- package/src/tui/app/shell-layout/surfaces.mjs +3 -1
- package/src/tui/app/slash-commands.mjs +19 -3
- package/src/tui/app/slash-dispatch/session-commands.mjs +68 -6
- package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
- package/src/tui/app/use-global-key-input.mjs +5 -12
- package/src/tui/app/use-prompt-handlers.mjs +11 -0
- package/src/tui/components/BtwPanel.jsx +53 -0
- package/src/tui/components/QuestionPanel.jsx +275 -0
- package/src/tui/components/Spinner.jsx +4 -0
- package/src/tui/components/SpinnerByline.jsx +21 -2
- package/src/tui/components/question-panel-model.mjs +156 -0
- package/src/tui/components/status-line/local-line.mjs +10 -2
- package/src/tui/dist/index.mjs +2897 -1722
- package/src/tui/session/active-tool-summary.mjs +1 -1
- package/src/tui/session/context-state.mjs +4 -5
- package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
- package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
- package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
- package/src/tui/session/quota-resume.mjs +140 -61
- package/src/tui/session/session-api/commands.mjs +14 -10
- package/src/tui/session/session-api/controls.mjs +14 -2
- package/src/tui/session/session-api/integrations.mjs +9 -2
- package/src/tui/session/session-api/lifecycle/dispose.mjs +3 -9
- package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
- package/src/tui/session/session-api/lifecycle.mjs +14 -11
- package/src/tui/session/session-api/media.mjs +2 -0
- package/src/tui/session/session-api/routes.mjs +81 -57
- package/src/tui/session/session-api/settings.mjs +9 -3
- package/src/tui/session/session-api/worktree.mjs +3 -2
- package/src/tui/session/session-api.mjs +1 -0
- package/src/tui/session/session-flow/auto-clear.mjs +1 -3
- package/src/tui/session/session-flow/drain.mjs +6 -4
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
- package/src/tui/session/session-flow/queue.mjs +1 -1
- package/src/tui/session/tool-approval.mjs +20 -8
- package/src/tui/session/transcript-spill.mjs +14 -7
- package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
- package/src/tui/session/tui-steering-persist.mjs +3 -5
- package/src/tui/session/turn-quiet.mjs +53 -0
- package/src/tui/session/turn-stream.mjs +179 -32
- package/src/tui/session/turn.mjs +50 -36
- package/src/tui/session/worktree-setup-notice.mjs +38 -0
- package/src/tui/spinner-meta.mjs +10 -0
- package/src/tui/spinner-verbs.mjs +3 -0
- package/src/ui/statusline.mjs +13 -3
- package/src/workflows/default/WORKFLOW.md +3 -1
- package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
- package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -246
- package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
- package/src/session-runtime/turn/share-ops.mjs +0 -77
- package/src/tui/session/cross-surface-share.mjs +0 -406
- package/src/tui/session/live-share/delta.mjs +0 -153
- package/src/tui/session/live-share/mirror.mjs +0 -106
- package/src/tui/session/live-share/owner.mjs +0 -254
- package/src/tui/session/live-share/retry.mjs +0 -54
- package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
- package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
- package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
- package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
- package/src/tui/session/live-share/viewer.mjs +0 -99
- package/src/tui/session/live-share/wire.mjs +0 -44
- package/src/tui/session/live-share.mjs +0 -154
- package/src/tui/session/shared-dir-watch.mjs +0 -69
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Managed git worktrees for Lead sessions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Automatic worktrees are created lazily from the project's current branch
|
|
5
|
+
* on "<branchPrefix><slug>" at managedWorktreePath(). Explicit branch selection
|
|
6
|
+
* reuses its checkout or creates one for that exact branch. Creator retention
|
|
7
|
+
* and additional sessions' durable references protect shared checkouts. Every
|
|
6
8
|
* mutation runs under withRepoLock (in-process git lock keyed by the common
|
|
7
9
|
* git dir + cross-process file lock) and is recorded in the durable registry
|
|
8
10
|
* (registry.mjs) before it touches anything:
|
|
@@ -26,8 +28,14 @@
|
|
|
26
28
|
* -d` forgets it).
|
|
27
29
|
*/
|
|
28
30
|
import { existsSync } from 'node:fs';
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
+
import {
|
|
32
|
+
assertRecoveryStoresClear,
|
|
33
|
+
recoveryScopes,
|
|
34
|
+
retainedRecoveryScopes,
|
|
35
|
+
validateRecoveryScope,
|
|
36
|
+
} from './file-recovery.mjs';
|
|
37
|
+
import { lstat, mkdir, readdir, readFile, rm, rmdir, stat } from 'node:fs/promises';
|
|
38
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
31
39
|
import {
|
|
32
40
|
managedWorktreePath,
|
|
33
41
|
managedWorktreeRoot,
|
|
@@ -37,6 +45,13 @@ import {
|
|
|
37
45
|
worktreeBranchName,
|
|
38
46
|
worktreeError,
|
|
39
47
|
} from './contract.mjs';
|
|
48
|
+
import {
|
|
49
|
+
assertCarryable,
|
|
50
|
+
carryUncommittedChanges,
|
|
51
|
+
dropRecoveryRef,
|
|
52
|
+
restoreFromRecoveryRef,
|
|
53
|
+
uncommittedChanges,
|
|
54
|
+
} from './carry.mjs';
|
|
40
55
|
import { freeBytesAt, requiredBytes, trackedCheckoutBytes } from './disk.mjs';
|
|
41
56
|
import {
|
|
42
57
|
branchCommit,
|
|
@@ -56,24 +71,39 @@ import {
|
|
|
56
71
|
applyIncludes,
|
|
57
72
|
coveredByIncludes,
|
|
58
73
|
modifiedIncludeCopies,
|
|
74
|
+
untrackedEnvFiles,
|
|
59
75
|
planIncludes,
|
|
60
76
|
readIncludeEntries,
|
|
61
77
|
removeIncludeLinks,
|
|
78
|
+
restoreIncludeLinks,
|
|
62
79
|
} from './include.mjs';
|
|
80
|
+
import { linkWorktreeDependencies } from './dependencies.mjs';
|
|
63
81
|
import { withRepoLock as withRepoMutationLock } from './lock.mjs';
|
|
64
|
-
import { mergeBranchIntoCheckout } from './merge.mjs';
|
|
82
|
+
import { mergeBranchIntoCheckout, operationInProgress } from './merge.mjs';
|
|
65
83
|
import { readOwnerLease, removeOwnerLease, sameOwner, writeOwnerLease } from './owner.mjs';
|
|
66
84
|
import { canonicalPath, checkoutFolder, isWithin, pathKey, projectSubdirOf, samePath } from './paths.mjs';
|
|
67
|
-
import {
|
|
85
|
+
import {
|
|
86
|
+
deleteRecord,
|
|
87
|
+
entryView,
|
|
88
|
+
getRecord,
|
|
89
|
+
insertRecord,
|
|
90
|
+
putRecord,
|
|
91
|
+
readRegistry,
|
|
92
|
+
registryPath,
|
|
93
|
+
} from './registry.mjs';
|
|
68
94
|
import { normalizeWorktreeSettings } from './settings.mjs';
|
|
69
95
|
import { slugFromHint, uniqueSlug } from './slug.mjs';
|
|
70
96
|
|
|
71
97
|
const WORKTREE_ADD_TIMEOUT_MS = 30 * 60_000;
|
|
72
98
|
const REMOVE_RETRIES = 6;
|
|
73
99
|
const REMOVE_RETRY_DELAY_MS = 150;
|
|
100
|
+
// Entries a folder-only removal checks for unrecorded directory links.
|
|
101
|
+
const LINK_SCAN_LIMIT = 250_000;
|
|
74
102
|
// The session copies the project identity into each managed worktree; it is
|
|
75
103
|
// Mixdog metadata, not user data, so it alone never makes a worktree dirty.
|
|
76
104
|
const SESSION_PROJECT_ID_PATH = '.mixdog/project.id';
|
|
105
|
+
// Changed paths WORKTREE_DIRTY_CHOICE lists for the dialog's preview (the count is exact).
|
|
106
|
+
const DIRTY_CHOICE_PREVIEW = 50;
|
|
77
107
|
|
|
78
108
|
export { registerRootReleaser } from './contract.mjs';
|
|
79
109
|
|
|
@@ -314,6 +344,7 @@ async function dirtyPaths(record) {
|
|
|
314
344
|
}
|
|
315
345
|
|
|
316
346
|
async function deleteBranchIfFree(record) {
|
|
347
|
+
if (record.existingBranch === true) return { deleted: false, reason: 'pre-existing' };
|
|
317
348
|
const cwd = repoCwd(record);
|
|
318
349
|
if (!(await branchCommit(cwd, record.branch))) return { deleted: false, reason: 'missing' };
|
|
319
350
|
const holder = (await listWorktrees(cwd)).find((worktree) => worktree.branch === record.branch);
|
|
@@ -327,7 +358,12 @@ async function deleteBranchIfFree(record) {
|
|
|
327
358
|
* still present (empty when everything is gone).
|
|
328
359
|
*/
|
|
329
360
|
async function removeCheckout(record) {
|
|
361
|
+
await assertRecoveryStoresClear(record);
|
|
330
362
|
await assertManagedCheckoutPath(record);
|
|
363
|
+
// `git worktree remove --force` can descend through a junction on Windows
|
|
364
|
+
// (fs.rm below does not: it unlinks links), so an unknown directory link
|
|
365
|
+
// refuses this removal as it does the folder-only one.
|
|
366
|
+
await assertNoUnrecordedDirectoryLinks(record);
|
|
331
367
|
const remaining = [];
|
|
332
368
|
for (const path of await removeIncludeLinks(record.resources?.includes)) remaining.push({ kind: 'link', path });
|
|
333
369
|
if (remaining.length) return remaining;
|
|
@@ -354,6 +390,128 @@ async function removeCheckout(record) {
|
|
|
354
390
|
return remaining;
|
|
355
391
|
}
|
|
356
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Refuse a checkout that holds a directory link the ledger does not know (a
|
|
395
|
+
* junction or directory symlink made by the user or a build tool): `git
|
|
396
|
+
* worktree remove` can descend through it on Windows and delete the target's
|
|
397
|
+
* files. Refusing is chosen over unlinking: nothing the manager did not make
|
|
398
|
+
* is ever touched, and the user removes the link (never its target) and
|
|
399
|
+
* retries. The walk never follows a link, skips `.git` entries (the back link
|
|
400
|
+
* file, nested repositories' internals) and is bounded: a checkout too large
|
|
401
|
+
* to verify is refused as well. Recorded links are skipped (unlinked next).
|
|
402
|
+
*/
|
|
403
|
+
async function assertNoUnrecordedDirectoryLinks(record) {
|
|
404
|
+
const key = (path) => (process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path));
|
|
405
|
+
const recorded = new Set(
|
|
406
|
+
(record.resources?.includes || []).filter((item) => item.mode === 'link').map((item) => key(item.path))
|
|
407
|
+
);
|
|
408
|
+
const refuse = (message, details) =>
|
|
409
|
+
worktreeError('WORKTREE_PATH_UNSAFE', message, { entryId: record.id, path: record.path, ...details });
|
|
410
|
+
const found = [];
|
|
411
|
+
const pending = [record.path];
|
|
412
|
+
let scanned = 0;
|
|
413
|
+
while (pending.length) {
|
|
414
|
+
const dir = pending.pop();
|
|
415
|
+
let entries;
|
|
416
|
+
try {
|
|
417
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
418
|
+
} catch (error) {
|
|
419
|
+
if (error?.code === 'ENOENT') continue;
|
|
420
|
+
throw refuse(
|
|
421
|
+
`The worktree folder could not be checked for links (${error?.code || error?.message}); nothing was deleted.`,
|
|
422
|
+
{ reason: 'unreadable' }
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
if (++scanned > LINK_SCAN_LIMIT) {
|
|
427
|
+
throw refuse(
|
|
428
|
+
`The worktree folder has more than ${LINK_SCAN_LIMIT} entries to check for links; nothing was deleted.`,
|
|
429
|
+
{ reason: 'too-many-entries' }
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
if (entry.name.toLowerCase() === '.git') continue;
|
|
433
|
+
const path = join(dir, entry.name);
|
|
434
|
+
// A junction is a link here too (a reparse point), so it is never descended.
|
|
435
|
+
if (entry.isDirectory()) pending.push(path);
|
|
436
|
+
else if (entry.isSymbolicLink() && !recorded.has(key(path)) && (await linksToDirectory(path))) {
|
|
437
|
+
found.push(relative(record.path, path).split(sep).join('/'));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (found.length) {
|
|
442
|
+
throw refuse(
|
|
443
|
+
`The worktree contains linked folders it did not create (${found.slice(0, 10).join(', ')}${found.length > 10 ? ', …' : ''}). ` +
|
|
444
|
+
'Removing the folder could delete their targets: remove those links (not their targets) first; nothing was deleted.',
|
|
445
|
+
{ reason: 'unrecorded-links', links: found.slice(0, 50), count: found.length }
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** True when the link at `path` resolves to a directory (or cannot be ruled out). */
|
|
451
|
+
async function linksToDirectory(path) {
|
|
452
|
+
try {
|
|
453
|
+
return (await stat(path)).isDirectory();
|
|
454
|
+
} catch (error) {
|
|
455
|
+
// Dangling: nothing to descend into.
|
|
456
|
+
return error?.code !== 'ENOENT' && error?.code !== 'ENOTDIR';
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Explicit folder-only cleanup never force-removes a checkout or user files. */
|
|
461
|
+
async function removeCheckoutSafely(record) {
|
|
462
|
+
const registration = await registrationFor(record);
|
|
463
|
+
if (!registration && !existsSync(record.path)) return [];
|
|
464
|
+
await assertManagedCheckoutPath(record);
|
|
465
|
+
await assertRecoveryStoresClear(record);
|
|
466
|
+
await assertRemovable(record);
|
|
467
|
+
if (await operationInProgress(gitStatusRunner, record.path)) {
|
|
468
|
+
throw worktreeError('WORKTREE_DIRTY', 'Finish the Git operation before removing this worktree.');
|
|
469
|
+
}
|
|
470
|
+
const ownedLock = `mixdog session=${record.ownerSessionId}`;
|
|
471
|
+
if (registration?.locked !== null && registration?.locked !== ownedLock) {
|
|
472
|
+
throw worktreeError('WORKTREE_BUSY_JOBS', 'The worktree has an external Git lock.');
|
|
473
|
+
}
|
|
474
|
+
// `git worktree remove` descends through a directory junction on Windows
|
|
475
|
+
// and would empty its target (a linked node_modules in the main checkout):
|
|
476
|
+
// an unknown link refuses the removal, the recorded links go first, by unlink only.
|
|
477
|
+
await assertNoUnrecordedDirectoryLinks(record);
|
|
478
|
+
const includes = record.resources?.includes;
|
|
479
|
+
if ((await removeIncludeLinks(includes)).length) {
|
|
480
|
+
const failures = await restoreIncludeLinks(includes);
|
|
481
|
+
throw withRollbackFailures(
|
|
482
|
+
worktreeError('WORKTREE_BUSY_JOBS', 'A linked folder in the worktree could not be unlinked.'),
|
|
483
|
+
failures.map((failure) => `${failure.path} (${failure.error})`)
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
let unlocked = false;
|
|
487
|
+
try {
|
|
488
|
+
if (registration?.locked === ownedLock) {
|
|
489
|
+
await runGit(repoCwd(record), ['worktree', 'unlock', record.path]);
|
|
490
|
+
unlocked = true;
|
|
491
|
+
}
|
|
492
|
+
await runGit(repoCwd(record), ['worktree', 'remove', record.path]);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
const failures = (await restoreIncludeLinks(includes)).map((failure) => `${failure.path} (${failure.error})`);
|
|
495
|
+
if (unlocked && (await registrationFor(record))) {
|
|
496
|
+
try {
|
|
497
|
+
await runGit(repoCwd(record), ['worktree', 'lock', '--reason', ownedLock, record.path]);
|
|
498
|
+
} catch (lockError) {
|
|
499
|
+
failures.push(`worktree lock (${firstLine(lockError?.message) || lockError})`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
throw withRollbackFailures(error, failures);
|
|
503
|
+
}
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Report what a failed safe removal could not put back on its error (never swallowed). */
|
|
508
|
+
function withRollbackFailures(error, failures) {
|
|
509
|
+
if (!failures.length) return error;
|
|
510
|
+
error.rollbackFailures = failures;
|
|
511
|
+
error.message = `${error.message} Not restored: ${failures.join(', ')}.`;
|
|
512
|
+
return error;
|
|
513
|
+
}
|
|
514
|
+
|
|
357
515
|
/**
|
|
358
516
|
* After the last worktree of a repository is gone, drop its now-empty store
|
|
359
517
|
* folder (<dataDir>/worktrees-v1/<repoHash>/ and its .owners/). Runs under the
|
|
@@ -399,7 +557,11 @@ async function rollbackCreation(record, cause) {
|
|
|
399
557
|
if (resources.worktree) {
|
|
400
558
|
const left = await removeCheckout(record);
|
|
401
559
|
remaining.push(...left);
|
|
402
|
-
if (
|
|
560
|
+
if (
|
|
561
|
+
!left.length &&
|
|
562
|
+
record.existingBranch !== true &&
|
|
563
|
+
!(record.exactBranch === true && resources.worktree !== 'created')
|
|
564
|
+
) {
|
|
403
565
|
// The slug was free when this transaction reserved it, so a branch
|
|
404
566
|
// still at the base commit is the one `worktree add -b` made. A
|
|
405
567
|
// branch that moved holds someone's commits and is kept.
|
|
@@ -437,6 +599,7 @@ async function rollbackCreation(record, cause) {
|
|
|
437
599
|
* and retried with the deletion; it never keeps the checkout.
|
|
438
600
|
*/
|
|
439
601
|
async function finishDeletion(record) {
|
|
602
|
+
await assertRecoveryStoresClear(record);
|
|
440
603
|
const remaining = [];
|
|
441
604
|
try {
|
|
442
605
|
await removeOwnerLease(record);
|
|
@@ -448,7 +611,7 @@ async function finishDeletion(record) {
|
|
|
448
611
|
for (const result of results) {
|
|
449
612
|
if (!result.ok) remaining.push({ kind: 'cleanup', name: result.name, error: result.error.message });
|
|
450
613
|
}
|
|
451
|
-
remaining.push(...(await removeCheckout(record)));
|
|
614
|
+
remaining.push(...(await (record.deleting?.safeOnly ? removeCheckoutSafely(record) : removeCheckout(record))));
|
|
452
615
|
} catch (error) {
|
|
453
616
|
remaining.push({ kind: 'error', error: String(error?.message || error) });
|
|
454
617
|
}
|
|
@@ -460,7 +623,7 @@ async function finishDeletion(record) {
|
|
|
460
623
|
let branch = { deleted: false, reason: 'kept' };
|
|
461
624
|
let kept = { tip: null, recoveryRef: null };
|
|
462
625
|
const merged = (await unmergedCommitCount(record)) === 0;
|
|
463
|
-
if (record.deleting?.force || merged) {
|
|
626
|
+
if (!record.deleting?.preserveBranch && (record.deleting?.force || merged)) {
|
|
464
627
|
// Forced past unmerged commits: the tip stays reachable before `branch -D`.
|
|
465
628
|
if (!merged) kept = await keepRemovedTip(record);
|
|
466
629
|
else kept.tip = await branchCommit(repoCwd(record), record.branch);
|
|
@@ -509,6 +672,100 @@ export async function getWorktreeByPath(path) {
|
|
|
509
672
|
return record ? entryView(record) : null;
|
|
510
673
|
}
|
|
511
674
|
|
|
675
|
+
/** Resolve a branch to its checkout without switching any existing folder. */
|
|
676
|
+
export async function resolveBranchWorkspace({
|
|
677
|
+
projectPath,
|
|
678
|
+
branch,
|
|
679
|
+
create = false,
|
|
680
|
+
sessionId,
|
|
681
|
+
runtimeToken,
|
|
682
|
+
txId,
|
|
683
|
+
confirmLarge = false,
|
|
684
|
+
signal,
|
|
685
|
+
} = {}) {
|
|
686
|
+
requireText(branch, 'branch');
|
|
687
|
+
const repo = await inspectRepo(projectPath, { signal });
|
|
688
|
+
return withRepoLock(repo.repoCommonDir, async () => {
|
|
689
|
+
if (!(await validBranchName(repo.projectPath, branch))) throw new TypeError('Invalid branch name.');
|
|
690
|
+
const records = Object.values((await readRegistry()).entries);
|
|
691
|
+
const transaction = records.find((record) => record.txId === txId);
|
|
692
|
+
if (
|
|
693
|
+
transaction &&
|
|
694
|
+
(transaction.ownerSessionId !== sessionId ||
|
|
695
|
+
transaction.branch !== branch ||
|
|
696
|
+
!samePath(transaction.repoCommonDir, repo.repoCommonDir))
|
|
697
|
+
) {
|
|
698
|
+
throw worktreeError('WORKTREE_REVISION_CONFLICT', 'This transaction belongs to another workspace.');
|
|
699
|
+
}
|
|
700
|
+
const retry =
|
|
701
|
+
transaction ??
|
|
702
|
+
(create
|
|
703
|
+
? records.find(
|
|
704
|
+
(record) =>
|
|
705
|
+
record.ownerSessionId === sessionId &&
|
|
706
|
+
record.branch === branch &&
|
|
707
|
+
record.exactBranch === true &&
|
|
708
|
+
samePath(record.repoCommonDir, repo.repoCommonDir)
|
|
709
|
+
)
|
|
710
|
+
: null);
|
|
711
|
+
if (retry?.state === 'ready') {
|
|
712
|
+
if (!existsSync(retry.path)) throw worktreeError('WORKTREE_MISSING');
|
|
713
|
+
await claimWorktreeOwner(retry.id, { sessionId, runtimeToken, pid: process.pid });
|
|
714
|
+
return { path: retry.path, branch, entry: entryView(retry), repoCommonDir: repo.repoCommonDir };
|
|
715
|
+
}
|
|
716
|
+
const branches = await localBranches(repo.projectPath, { signal });
|
|
717
|
+
if (create && branches.includes(branch))
|
|
718
|
+
throw worktreeError('WORKTREE_REVISION_CONFLICT', 'Branch already exists.');
|
|
719
|
+
if (!create && !branches.includes(branch)) throw worktreeError('WORKTREE_MISSING', 'Branch does not exist.');
|
|
720
|
+
const holder = (await listWorktrees(repo.projectPath)).find((item) => item.branch === branch && !item.prunable);
|
|
721
|
+
if (holder) {
|
|
722
|
+
if (!existsSync(holder.path)) throw worktreeError('WORKTREE_MISSING');
|
|
723
|
+
const entry = await getWorktreeByPath(holder.path);
|
|
724
|
+
const claim = entry ? await claimWorktreeOwner(entry.id, { sessionId, runtimeToken, pid: process.pid }) : null;
|
|
725
|
+
return {
|
|
726
|
+
path: holder.path,
|
|
727
|
+
branch,
|
|
728
|
+
entry,
|
|
729
|
+
referenceAdded: claim?.referenceAdded === true,
|
|
730
|
+
repoCommonDir: repo.repoCommonDir,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const entry = await createManagedWorktree({
|
|
734
|
+
projectPath,
|
|
735
|
+
sessionId,
|
|
736
|
+
runtimeToken,
|
|
737
|
+
txId,
|
|
738
|
+
branch,
|
|
739
|
+
existingBranch: !create,
|
|
740
|
+
baseBranch: create ? repo.currentBranch : branch,
|
|
741
|
+
nameHint: branch,
|
|
742
|
+
confirmLarge,
|
|
743
|
+
signal,
|
|
744
|
+
});
|
|
745
|
+
const record = await getRecord(entry.id);
|
|
746
|
+
record.mergeTarget = repo.currentBranch;
|
|
747
|
+
await putRecord(record);
|
|
748
|
+
entry.mergeTarget = repo.currentBranch;
|
|
749
|
+
return { path: entry.path, branch, entry, created: true, repoCommonDir: repo.repoCommonDir };
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** Remove only a deleted session's shared references, never its neighbours'. */
|
|
754
|
+
export async function forgetSharedWorktreeSession(sessionId) {
|
|
755
|
+
for (const record of Object.values((await readRegistry()).entries)) {
|
|
756
|
+
if (!record.sharedSessionIds?.includes(sessionId)) continue;
|
|
757
|
+
await withRepoLock(record.repoCommonDir, async () => {
|
|
758
|
+
const current = await getRecord(record.id);
|
|
759
|
+
if (!current) return;
|
|
760
|
+
current.sharedSessionIds = (current.sharedSessionIds ?? []).filter((id) => id !== sessionId);
|
|
761
|
+
const leases = { ...current.sharedLeases };
|
|
762
|
+
delete leases[sessionId];
|
|
763
|
+
current.sharedLeases = leases;
|
|
764
|
+
await putRecord(current);
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
512
769
|
/** The managed worktree whose checkout is `path` or contains it (a subfolder Project's session root), or null. */
|
|
513
770
|
export async function findWorktreeContaining(path) {
|
|
514
771
|
if (typeof path !== 'string' || !path.trim()) return null;
|
|
@@ -516,6 +773,50 @@ export async function findWorktreeContaining(path) {
|
|
|
516
773
|
return record ? entryView(record) : null;
|
|
517
774
|
}
|
|
518
775
|
|
|
776
|
+
/** File-host seam, not a user capability. Persist the host-owned scope before
|
|
777
|
+
* preparing a byte journal, and hold the same repository lock as every removal
|
|
778
|
+
* through the operation. The descriptor survives daemon exit/startup reconcile.
|
|
779
|
+
* @template T
|
|
780
|
+
* @param {string} root
|
|
781
|
+
* @param {string} directory
|
|
782
|
+
* @param {'write'|'restore'|'inspect'|'discard'} action
|
|
783
|
+
* @param {() => Promise<T>} run
|
|
784
|
+
* @returns {Promise<T>}
|
|
785
|
+
*/
|
|
786
|
+
export async function withWorktreeRecoveryStore(root, directory, action, run) {
|
|
787
|
+
const entry = await findWorktreeContaining(root);
|
|
788
|
+
if (!entry) {
|
|
789
|
+
// A managed checkout can disappear between host authorization and this
|
|
790
|
+
// lookup. Its reserved namespace must never become an ordinary project.
|
|
791
|
+
// Retain a lexical check as well, so a replacement link cannot escape it.
|
|
792
|
+
const store = dirname(registryPath());
|
|
793
|
+
const lexicalKey = (path) => (process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path));
|
|
794
|
+
const requested = lexicalKey(root);
|
|
795
|
+
if (
|
|
796
|
+
isWithin(store, root) ||
|
|
797
|
+
[store, canonicalPath(store)].some(
|
|
798
|
+
(path) => requested === lexicalKey(path) || requested.startsWith(lexicalKey(path) + sep)
|
|
799
|
+
)
|
|
800
|
+
)
|
|
801
|
+
throw worktreeError('WORKTREE_MISSING', undefined, { path: root });
|
|
802
|
+
return run();
|
|
803
|
+
}
|
|
804
|
+
return withRepoLock(entry.repoCommonDir, async () => {
|
|
805
|
+
const record = await getRecord(entry.id);
|
|
806
|
+
if (!record || ((action === 'write' || action === 'restore') && record.state !== 'ready'))
|
|
807
|
+
throw worktreeError('WORKTREE_MISSING', undefined, { entryId: entry.id });
|
|
808
|
+
const scope = validateRecoveryScope(record, { root, directory });
|
|
809
|
+
let scopes = recoveryScopes(record);
|
|
810
|
+
if (!scopes.some((item) => item.root === root && item.directory === directory)) {
|
|
811
|
+
if (scopes.length >= 64) scopes = await retainedRecoveryScopes(record);
|
|
812
|
+
if (scopes.length >= 64) throw worktreeError('WORKTREE_FILE_RECOVERY');
|
|
813
|
+
record.editorRecoveryStores = [...scopes, scope];
|
|
814
|
+
await putRecord(record);
|
|
815
|
+
}
|
|
816
|
+
return run();
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
519
820
|
/** Mark an entry disposable: startup reconcile may remove it once clean and merged. */
|
|
520
821
|
export async function markWorktreeDisposable(entryId, disposable = true) {
|
|
521
822
|
const record = await getRecord(entryId);
|
|
@@ -536,7 +837,7 @@ export async function markWorktreeDisposable(entryId, disposable = true) {
|
|
|
536
837
|
* WORKTREE_REVISION_CONFLICT for another session's transaction, a ready
|
|
537
838
|
* worktree of another repository, or an attempt that is still being cleaned up.
|
|
538
839
|
*/
|
|
539
|
-
async function settleSessionAttempts({ txId, owner, repoCommonDir }) {
|
|
840
|
+
async function settleSessionAttempts({ txId, owner, repoCommonDir, multiple = false }) {
|
|
540
841
|
const doc = await readRegistry();
|
|
541
842
|
const sameRepo = (record) => samePath(record.repoCommonDir, repoCommonDir);
|
|
542
843
|
|
|
@@ -550,6 +851,7 @@ async function settleSessionAttempts({ txId, owner, repoCommonDir }) {
|
|
|
550
851
|
}
|
|
551
852
|
|
|
552
853
|
for (const record of Object.values(doc.entries).filter((item) => item.ownerSessionId === owner.sessionId)) {
|
|
854
|
+
if (multiple && record.txId !== txId) continue;
|
|
553
855
|
if (record.state === 'ready') {
|
|
554
856
|
if (!sameRepo(record)) {
|
|
555
857
|
throw worktreeError(
|
|
@@ -600,8 +902,20 @@ async function diskFullFailure(error, { needed, root }) {
|
|
|
600
902
|
}
|
|
601
903
|
|
|
602
904
|
/**
|
|
905
|
+
* `carryChanges` decides the project folder's uncommitted changes (tracked
|
|
906
|
+
* and untracked, ignored excluded), judged under the repository lock:
|
|
907
|
+
* omitted/null leaves them unexamined; `ask` refuses with
|
|
908
|
+
* WORKTREE_DIRTY_CHOICE (`details: { count, paths }`, paths capped) while
|
|
909
|
+
* there are any; `leave` keeps them in the project folder; `carry` moves
|
|
910
|
+
* them into the new worktree (carry.mjs: whole checkout, ignored files stay)
|
|
911
|
+
* — refused (WORKTREE_DIRTY) during a merge, rebase or cherry-pick, with
|
|
912
|
+
* unmerged entries, dirty submodules, filtered (LFS) paths or oversized
|
|
913
|
+
* untracked files. The result's `changes` (`{ mode: 'carried'|'left',
|
|
914
|
+
* count, recoveryRef? }`) reports what happened to them; a carried move's
|
|
915
|
+
* `recoveryRef` stays until dropCarryRecoveryRef / restoreCarriedChanges.
|
|
603
916
|
* @param {{ projectPath: string, sessionId: string, runtimeToken: string, baseBranch?: string|null,
|
|
604
|
-
* nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal
|
|
917
|
+
* nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal,
|
|
918
|
+
* carryChanges?: 'ask'|'leave'|'carry'|null }} input
|
|
605
919
|
*/
|
|
606
920
|
export async function createManagedWorktree({
|
|
607
921
|
projectPath,
|
|
@@ -612,17 +926,40 @@ export async function createManagedWorktree({
|
|
|
612
926
|
confirmLarge = false,
|
|
613
927
|
txId,
|
|
614
928
|
signal,
|
|
929
|
+
carryChanges = null,
|
|
930
|
+
branch: requestedBranch = null,
|
|
931
|
+
existingBranch = false,
|
|
615
932
|
} = {}) {
|
|
616
933
|
requireText(projectPath, 'projectPath');
|
|
617
934
|
requireText(txId, 'txId');
|
|
935
|
+
if (![null, 'ask', 'leave', 'carry'].includes(carryChanges)) {
|
|
936
|
+
throw new TypeError('carryChanges must be ask, leave or carry.');
|
|
937
|
+
}
|
|
618
938
|
const owner = normalizeWorktreeOwner({ sessionId, runtimeToken, pid: process.pid });
|
|
619
939
|
throwIfAborted(signal);
|
|
620
940
|
const repo = await inspectRepo(projectPath, { signal });
|
|
621
941
|
return withRepoLock(repo.repoCommonDir, async () => {
|
|
622
|
-
const existing = await settleSessionAttempts({
|
|
942
|
+
const existing = await settleSessionAttempts({
|
|
943
|
+
txId,
|
|
944
|
+
owner,
|
|
945
|
+
repoCommonDir: repo.repoCommonDir,
|
|
946
|
+
multiple: requestedBranch !== null,
|
|
947
|
+
});
|
|
623
948
|
if (existing) return existing;
|
|
624
949
|
throwIfAborted(signal);
|
|
625
950
|
|
|
951
|
+
const projectSubdir = projectSubdirOf(repo.topLevel, repo.projectPath);
|
|
952
|
+
// A carry is judged by the same status read that counts the changes.
|
|
953
|
+
let pendingChanges = [];
|
|
954
|
+
if (carryChanges === 'carry') pendingChanges = await assertCarryable(repo.topLevel, projectSubdir);
|
|
955
|
+
else if (carryChanges) pendingChanges = await uncommittedChanges(repo.topLevel, projectSubdir);
|
|
956
|
+
if (pendingChanges.length && carryChanges === 'ask') {
|
|
957
|
+
throw worktreeError('WORKTREE_DIRTY_CHOICE', undefined, {
|
|
958
|
+
count: pendingChanges.length,
|
|
959
|
+
paths: pendingChanges.slice(0, DIRTY_CHOICE_PREVIEW),
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
626
963
|
const settings = await currentWorktreeSettings();
|
|
627
964
|
let baseCommit = repo.headCommit;
|
|
628
965
|
let mergeTarget = repo.currentBranch;
|
|
@@ -634,7 +971,11 @@ export async function createManagedWorktree({
|
|
|
634
971
|
mergeTarget = baseBranch;
|
|
635
972
|
}
|
|
636
973
|
|
|
637
|
-
|
|
974
|
+
// The main checkout's local env files come along on their own;
|
|
975
|
+
// `.worktreeinclude` applies on top.
|
|
976
|
+
const includePlan = await planIncludes(repo.topLevel, await readIncludeEntries(repo.topLevel), {
|
|
977
|
+
envFiles: await untrackedEnvFiles(repo.topLevel),
|
|
978
|
+
});
|
|
638
979
|
const tracked = await trackedCheckoutBytes(repo.topLevel, baseCommit, { signal });
|
|
639
980
|
const contentBytes = tracked.bytes + includePlan.copyBytes;
|
|
640
981
|
const root = managedWorktreeRoot(repo.repoCommonDir);
|
|
@@ -670,11 +1011,18 @@ export async function createManagedWorktree({
|
|
|
670
1011
|
return reserved;
|
|
671
1012
|
};
|
|
672
1013
|
const addCheckout = (target) =>
|
|
673
|
-
runGit(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1014
|
+
runGit(
|
|
1015
|
+
repo.projectPath,
|
|
1016
|
+
existingBranch
|
|
1017
|
+
? ['worktree', 'add', target.path, target.branch]
|
|
1018
|
+
: ['worktree', 'add', '-b', target.branch, target.path, baseCommit],
|
|
1019
|
+
{
|
|
1020
|
+
signal,
|
|
1021
|
+
timeoutMs: WORKTREE_ADD_TIMEOUT_MS,
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
677
1024
|
let record = await reserve();
|
|
1025
|
+
let carried = null;
|
|
678
1026
|
|
|
679
1027
|
try {
|
|
680
1028
|
await mkdir(root, { recursive: true });
|
|
@@ -682,6 +1030,7 @@ export async function createManagedWorktree({
|
|
|
682
1030
|
try {
|
|
683
1031
|
await addCheckout(record);
|
|
684
1032
|
} catch (error) {
|
|
1033
|
+
if (requestedBranch) throw error;
|
|
685
1034
|
// Another process took the branch name (or folder) after the
|
|
686
1035
|
// reservation: git refused before making anything, so reserve the
|
|
687
1036
|
// next free name once and try again.
|
|
@@ -708,16 +1057,65 @@ export async function createManagedWorktree({
|
|
|
708
1057
|
await setResource(record, 'owner', 'pending');
|
|
709
1058
|
await writeOwnerLease(record, owner);
|
|
710
1059
|
await setResource(record, 'owner', 'created');
|
|
1060
|
+
// Carried before the include entries are copied, so an include never
|
|
1061
|
+
// stands in the way of a carried file; the status is read again here,
|
|
1062
|
+
// right before acting.
|
|
1063
|
+
let changes = pendingChanges.length ? { mode: 'left', count: pendingChanges.length } : null;
|
|
1064
|
+
if (carryChanges === 'carry') {
|
|
1065
|
+
carried = await carryUncommittedChanges({
|
|
1066
|
+
topLevel: repo.topLevel,
|
|
1067
|
+
projectSubdir,
|
|
1068
|
+
worktreePath: record.path,
|
|
1069
|
+
baseCommit,
|
|
1070
|
+
label: record.id,
|
|
1071
|
+
});
|
|
1072
|
+
// The recovery ref stays until the caller has committed the move
|
|
1073
|
+
// (dropCarryRecoveryRef) or put the changes back (restoreCarriedChanges).
|
|
1074
|
+
changes = carried.count ? { mode: 'carried', count: carried.count, recoveryRef: carried.recoveryRef } : null;
|
|
1075
|
+
}
|
|
711
1076
|
const applied = await applyIncludes(includePlan, record.path, (ledger) => recordInclude(record, ledger));
|
|
712
1077
|
throwIfAborted(signal);
|
|
1078
|
+
// After the includes: one that already brought a node_modules wins.
|
|
1079
|
+
const linkedDependencies = await linkWorktreeDependencies({
|
|
1080
|
+
mainDir: repo.projectPath,
|
|
1081
|
+
worktreeDir: projectSubdir ? join(record.path, ...projectSubdir.split('/')) : record.path,
|
|
1082
|
+
worktreeRel: projectSubdir,
|
|
1083
|
+
signal,
|
|
1084
|
+
record: (ledger) => recordInclude(record, ledger),
|
|
1085
|
+
});
|
|
1086
|
+
throwIfAborted(signal);
|
|
713
1087
|
record.warnings = [...record.warnings, ...applied.warnings];
|
|
714
1088
|
record.state = 'ready';
|
|
715
1089
|
await putRecord(record);
|
|
716
|
-
|
|
1090
|
+
const made = (record.resources.includes || []).filter((item) => item.status === 'created');
|
|
1091
|
+
const rels = (predicate) => made.filter(predicate).map((item) => item.rel);
|
|
1092
|
+
// What creation did, for the agent's note at the first turn (transitions.mjs).
|
|
1093
|
+
const prepared = {
|
|
1094
|
+
root: repo.topLevel,
|
|
1095
|
+
copied: rels((item) => item.mode === 'copy'),
|
|
1096
|
+
linked: rels((item) => item.mode === 'link' && !item.dependency),
|
|
1097
|
+
dependencies: linkedDependencies,
|
|
1098
|
+
skipped: [...includePlan.warnings, ...applied.warnings],
|
|
1099
|
+
};
|
|
1100
|
+
return { ...entryView(record), prepared, ...(changes ? { changes } : {}) };
|
|
717
1101
|
} catch (error) {
|
|
1102
|
+
// Carried already: the changes go back to the project folder before the
|
|
1103
|
+
// worktree that holds them is rolled back (the recovery ref keeps them
|
|
1104
|
+
// either way).
|
|
1105
|
+
let carryRestore = null;
|
|
1106
|
+
if (carried?.recoveryRef) {
|
|
1107
|
+
carryRestore = await restoreFromRecoveryRef(repo.topLevel, projectSubdir, carried.recoveryRef)
|
|
1108
|
+
.then(() => ({ restored: true, recoveryRef: carried.recoveryRef }))
|
|
1109
|
+
.catch((restoreError) => ({
|
|
1110
|
+
restored: false,
|
|
1111
|
+
recoveryRef: carried.recoveryRef,
|
|
1112
|
+
error: String(restoreError?.message || restoreError),
|
|
1113
|
+
}));
|
|
1114
|
+
}
|
|
718
1115
|
const rollback = await rollbackCreation(record, error);
|
|
719
1116
|
const failure = isDiskFullError(error) ? await diskFullFailure(error, { needed, root }) : error;
|
|
720
1117
|
if (!rollback.complete) failure.rollback = rollback;
|
|
1118
|
+
if (carryRestore) failure.carryRestore = carryRestore;
|
|
721
1119
|
throw failure;
|
|
722
1120
|
}
|
|
723
1121
|
|
|
@@ -730,7 +1128,7 @@ export async function createManagedWorktree({
|
|
|
730
1128
|
branches.has(`${settings.branchPrefix}${candidate}`.toLowerCase()) ||
|
|
731
1129
|
existsSync(join(root, candidate))
|
|
732
1130
|
);
|
|
733
|
-
const branch = worktreeBranchName(slug, settings.branchPrefix);
|
|
1131
|
+
const branch = requestedBranch ?? worktreeBranchName(slug, settings.branchPrefix);
|
|
734
1132
|
if (!(await validBranchName(repo.projectPath, branch))) {
|
|
735
1133
|
throw worktreeError('WORKTREE_UNSUPPORTED_REPO', `Branch name ${branch} is not valid.`, { branch });
|
|
736
1134
|
}
|
|
@@ -746,6 +1144,8 @@ export async function createManagedWorktree({
|
|
|
746
1144
|
mergeTarget,
|
|
747
1145
|
createdAt: Date.now(),
|
|
748
1146
|
ownerSessionId: owner.sessionId,
|
|
1147
|
+
existingBranch,
|
|
1148
|
+
exactBranch: requestedBranch !== null,
|
|
749
1149
|
state: 'creating',
|
|
750
1150
|
disposable: false,
|
|
751
1151
|
warnings: [...(tracked.submodules ? ['submodules'] : []), ...includePlan.warnings],
|
|
@@ -758,6 +1158,30 @@ export async function createManagedWorktree({
|
|
|
758
1158
|
});
|
|
759
1159
|
}
|
|
760
1160
|
|
|
1161
|
+
/**
|
|
1162
|
+
* After a carry whose session move failed: put the changes kept at
|
|
1163
|
+
* `recoveryRef` back into the project folder (refused, ref kept, when the
|
|
1164
|
+
* folder changed meanwhile). The ref stays; drop it with dropCarryRecoveryRef.
|
|
1165
|
+
* @param {{ projectPath: string, recoveryRef: string }} input
|
|
1166
|
+
*/
|
|
1167
|
+
export async function restoreCarriedChanges({ projectPath, recoveryRef } = {}) {
|
|
1168
|
+
requireText(projectPath, 'projectPath');
|
|
1169
|
+
const repo = await inspectRepo(projectPath);
|
|
1170
|
+
return withRepoLock(repo.repoCommonDir, () =>
|
|
1171
|
+
restoreFromRecoveryRef(repo.topLevel, projectSubdirOf(repo.topLevel, repo.projectPath), recoveryRef)
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Forget a carry's recovery ref once the session move it protected is committed.
|
|
1177
|
+
* @param {{ projectPath: string, recoveryRef: string }} input
|
|
1178
|
+
*/
|
|
1179
|
+
export async function dropCarryRecoveryRef({ projectPath, recoveryRef } = {}) {
|
|
1180
|
+
requireText(projectPath, 'projectPath');
|
|
1181
|
+
const repo = await inspectRepo(projectPath);
|
|
1182
|
+
return withRepoLock(repo.repoCommonDir, () => dropRecoveryRef(repo.topLevel, recoveryRef));
|
|
1183
|
+
}
|
|
1184
|
+
|
|
761
1185
|
/**
|
|
762
1186
|
* Claim the live lease of a retained worktree for one runtime of its session.
|
|
763
1187
|
* @param {string} entryId
|
|
@@ -771,10 +1195,21 @@ export async function claimWorktreeOwner(entryId, owner) {
|
|
|
771
1195
|
const current = await getRecord(entryId);
|
|
772
1196
|
if (current?.state !== 'ready') throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
773
1197
|
if (current.ownerSessionId && current.ownerSessionId !== claimant.sessionId) {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1198
|
+
const references = new Set(current.sharedSessionIds ?? []);
|
|
1199
|
+
const referenceAdded = !references.has(claimant.sessionId);
|
|
1200
|
+
references.add(claimant.sessionId);
|
|
1201
|
+
current.sharedSessionIds = [...references];
|
|
1202
|
+
const previous = current.sharedLeases?.[claimant.sessionId] ?? null;
|
|
1203
|
+
const lease = { ...claimant, entryId, claimedAt: Date.now() };
|
|
1204
|
+
current.sharedLeases = { ...current.sharedLeases, [claimant.sessionId]: lease };
|
|
1205
|
+
current.disposable = false;
|
|
1206
|
+
await putRecord(current);
|
|
1207
|
+
return {
|
|
1208
|
+
entry: entryView(current),
|
|
1209
|
+
lease,
|
|
1210
|
+
referenceAdded,
|
|
1211
|
+
replaced: previous && !sameOwner(previous, claimant) ? previous : null,
|
|
1212
|
+
};
|
|
778
1213
|
}
|
|
779
1214
|
if (!current.ownerSessionId) {
|
|
780
1215
|
const other = findRecord(
|
|
@@ -799,13 +1234,23 @@ export async function claimWorktreeOwner(entryId, owner) {
|
|
|
799
1234
|
* Release the live lease when (and only when) it is held by exactly `owner`.
|
|
800
1235
|
* Durable ownership (the session retaining the worktree) is unchanged.
|
|
801
1236
|
*/
|
|
802
|
-
export async function releaseWorktreeOwner(entryId, owner) {
|
|
1237
|
+
export async function releaseWorktreeOwner(entryId, owner, { forgetReference = false } = {}) {
|
|
803
1238
|
const claimant = normalizeWorktreeOwner(owner);
|
|
804
1239
|
const record = await getRecord(entryId);
|
|
805
1240
|
if (!record) return { released: false };
|
|
806
1241
|
return withRepoLock(record.repoCommonDir, async () => {
|
|
807
1242
|
const current = await getRecord(entryId);
|
|
808
1243
|
if (!current) return { released: false };
|
|
1244
|
+
const shared = current.sharedLeases?.[claimant.sessionId];
|
|
1245
|
+
if (sameOwner(shared, claimant)) {
|
|
1246
|
+
const leases = { ...current.sharedLeases };
|
|
1247
|
+
delete leases[claimant.sessionId];
|
|
1248
|
+
current.sharedLeases = leases;
|
|
1249
|
+
if (forgetReference)
|
|
1250
|
+
current.sharedSessionIds = (current.sharedSessionIds ?? []).filter((id) => id !== claimant.sessionId);
|
|
1251
|
+
await putRecord(current);
|
|
1252
|
+
return { released: true };
|
|
1253
|
+
}
|
|
809
1254
|
const lease = await readOwnerLease(current);
|
|
810
1255
|
if (!sameOwner(lease, claimant)) return { released: false };
|
|
811
1256
|
await removeOwnerLease(current);
|
|
@@ -1051,15 +1496,39 @@ async function assertRemovable(record) {
|
|
|
1051
1496
|
}
|
|
1052
1497
|
}
|
|
1053
1498
|
|
|
1499
|
+
/** User changes, excluding the manager's own project identity and unchanged include copies. */
|
|
1500
|
+
export async function managedWorktreeDirtyPaths(entryId) {
|
|
1501
|
+
const record = await getRecord(entryId);
|
|
1502
|
+
if (!record) throw worktreeError('WORKTREE_MISSING');
|
|
1503
|
+
return dirtyPaths(record);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// Durable references protect sleeping sessions as well as running runtimes.
|
|
1507
|
+
// Force may discard files, never another session's workspace.
|
|
1508
|
+
function assertNotShared(record, sessionId = record.ownerSessionId) {
|
|
1509
|
+
const others = new Set(record.sharedSessionIds ?? []);
|
|
1510
|
+
if (record.ownerSessionId) others.add(record.ownerSessionId);
|
|
1511
|
+
if (sessionId) others.delete(sessionId);
|
|
1512
|
+
if (others.size) {
|
|
1513
|
+
throw worktreeError('WORKTREE_BUSY_JOBS', 'Other sessions retain this worktree; its folder must be kept.', {
|
|
1514
|
+
reason: 'workspace-shared',
|
|
1515
|
+
sessions: [...others],
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1054
1520
|
/**
|
|
1055
1521
|
* The refusal removeManagedWorktree would raise, checked before anything
|
|
1056
1522
|
* moves (a session leaves its worktree only for a removal that can proceed).
|
|
1057
|
-
* Unknown entries are WORKTREE_MISSING
|
|
1523
|
+
* Unknown entries are WORKTREE_MISSING. Recovery records block even `force`
|
|
1524
|
+
* and non-ready entries; ordinary dirty/unmerged checks apply only to ready ones.
|
|
1058
1525
|
* @param {{ entryId: string, force?: boolean }} input
|
|
1059
1526
|
*/
|
|
1060
|
-
export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
|
|
1527
|
+
export async function assertWorktreeRemovable({ entryId, force = false, sessionId } = {}) {
|
|
1061
1528
|
const record = await getRecord(entryId);
|
|
1062
1529
|
if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
1530
|
+
assertNotShared(record, sessionId);
|
|
1531
|
+
await assertRecoveryStoresClear(record);
|
|
1063
1532
|
if (record.state !== 'ready') return;
|
|
1064
1533
|
await assertManagedCheckoutPath(record);
|
|
1065
1534
|
if (!force) await assertRemovable(record);
|
|
@@ -1067,25 +1536,34 @@ export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
|
|
|
1067
1536
|
|
|
1068
1537
|
/**
|
|
1069
1538
|
* Remove a managed worktree. Refuses dirty (incl. untracked and modified
|
|
1070
|
-
* include copies) or unmerged worktrees unless `force`.
|
|
1539
|
+
* include copies) or unmerged worktrees unless `force`. Retained file recovery
|
|
1540
|
+
* data blocks removal regardless of force.
|
|
1071
1541
|
* @param {{ entryId: string, force?: boolean }} input
|
|
1072
1542
|
* @returns {Promise<{ removed: boolean, retryable?: boolean, remaining?: object[], branchDeleted?: boolean }>}
|
|
1073
1543
|
*/
|
|
1074
|
-
export async function removeManagedWorktree({
|
|
1544
|
+
export async function removeManagedWorktree({
|
|
1545
|
+
entryId,
|
|
1546
|
+
force = false,
|
|
1547
|
+
sessionId,
|
|
1548
|
+
preserveBranch = false,
|
|
1549
|
+
safeOnly = false,
|
|
1550
|
+
} = {}) {
|
|
1075
1551
|
const record = await getRecord(entryId);
|
|
1076
1552
|
if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
1077
1553
|
return withRepoLock(record.repoCommonDir, async () => {
|
|
1078
1554
|
const current = await getRecord(entryId);
|
|
1079
1555
|
if (!current) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
1556
|
+
assertNotShared(current, sessionId);
|
|
1080
1557
|
if (current.state === 'creating' || current.state === 'recovery') {
|
|
1081
1558
|
const rollback = await rollbackCreation(current);
|
|
1082
1559
|
return rollback.complete ? { removed: true } : { removed: false, retryable: true, remaining: rollback.remaining };
|
|
1083
1560
|
}
|
|
1084
1561
|
if (current.state === 'ready') {
|
|
1562
|
+
await assertRecoveryStoresClear(current);
|
|
1085
1563
|
await assertManagedCheckoutPath(current);
|
|
1086
1564
|
if (!force) await assertRemovable(current);
|
|
1087
1565
|
current.state = 'deleting';
|
|
1088
|
-
current.deleting = { force: force === true, startedAt: Date.now(), remaining: [] };
|
|
1566
|
+
current.deleting = { force: force === true, preserveBranch, safeOnly, startedAt: Date.now(), remaining: [] };
|
|
1089
1567
|
await putRecord(current);
|
|
1090
1568
|
}
|
|
1091
1569
|
return finishDeletion(current);
|
|
@@ -1101,7 +1579,7 @@ function leaseProcessAlive(lease) {
|
|
|
1101
1579
|
* Every managed worktree with who keeps it: `retained` (its session is in
|
|
1102
1580
|
* `retainedSessionIds`, i.e. still in the session store), `ownerLive` (a
|
|
1103
1581
|
* live session or a running lease holder), and `removable` (what a prune
|
|
1104
|
-
* would remove: neither, or disposable and not live).
|
|
1582
|
+
* would remove: neither, or disposable and not live, with no retained recovery data).
|
|
1105
1583
|
* @param {{ liveSessionIds?: string[], retainedSessionIds?: string[] }} input
|
|
1106
1584
|
*/
|
|
1107
1585
|
export async function listManagedWorktrees({ liveSessionIds = [], retainedSessionIds = [] } = {}) {
|
|
@@ -1112,12 +1590,23 @@ export async function listManagedWorktrees({ liveSessionIds = [], retainedSessio
|
|
|
1112
1590
|
const ownerLive =
|
|
1113
1591
|
(!!record.ownerSessionId && live.has(record.ownerSessionId)) || leaseProcessAlive(await readOwnerLease(record));
|
|
1114
1592
|
const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
|
|
1593
|
+
let removable =
|
|
1594
|
+
!record.sharedSessionIds?.length &&
|
|
1595
|
+
(record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained)));
|
|
1596
|
+
if (removable) {
|
|
1597
|
+
try {
|
|
1598
|
+
await assertRecoveryStoresClear(record);
|
|
1599
|
+
} catch (error) {
|
|
1600
|
+
if (error?.code !== 'WORKTREE_FILE_RECOVERY') throw error;
|
|
1601
|
+
removable = false;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1115
1604
|
rows.push({
|
|
1116
1605
|
...entryView(record),
|
|
1117
1606
|
projectPath: record.projectPath,
|
|
1118
1607
|
retained: isRetained,
|
|
1119
1608
|
ownerLive,
|
|
1120
|
-
removable
|
|
1609
|
+
removable,
|
|
1121
1610
|
});
|
|
1122
1611
|
}
|
|
1123
1612
|
return rows;
|
|
@@ -1164,6 +1653,10 @@ export async function reconcileManagedWorktrees({
|
|
|
1164
1653
|
for (const record of records) {
|
|
1165
1654
|
const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
|
|
1166
1655
|
try {
|
|
1656
|
+
if (record.sharedSessionIds?.length) {
|
|
1657
|
+
report.kept.push({ id: record.id, reason: 'shared' });
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1167
1660
|
if (record.state === 'creating' || record.state === 'recovery') {
|
|
1168
1661
|
if (isRetained) {
|
|
1169
1662
|
report.kept.push({ id: record.id, reason: 'retained' });
|
|
@@ -1194,6 +1687,7 @@ export async function reconcileManagedWorktrees({
|
|
|
1194
1687
|
report.kept.push({ id: record.id, reason: dirty.length ? 'dirty' : 'unmerged' });
|
|
1195
1688
|
continue;
|
|
1196
1689
|
}
|
|
1690
|
+
await assertRecoveryStoresClear(record);
|
|
1197
1691
|
record.state = 'deleting';
|
|
1198
1692
|
record.deleting = { force: false, startedAt: Date.now(), remaining: [] };
|
|
1199
1693
|
await putRecord(record);
|