mixdog 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/package.json +2 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/scripts/provider-pingpong-live.mjs +509 -0
- package/src/agents/advisor/AGENT.md +1 -1
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +6 -0
- 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 +7 -4
- package/src/output-styles/common.md +11 -10
- package/src/repl/slash-commands.mjs +16 -1
- package/src/rules/agent/AGENT.md +5 -5
- package/src/rules/lead/LEAD.md +5 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
- 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/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
- 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 +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
- 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 +75 -10
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -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 +80 -18
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
- package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
- 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 +246 -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 +388 -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/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 +48 -22
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -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 +33 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
- 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 +27 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -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 +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -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 +151 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
- 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 +172 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
- 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 +43 -15
- 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 +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- 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 -35
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
- 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-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
- 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 +157 -0
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -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 +183 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
- 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 +14 -3
- 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 +176 -71
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
- package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
- package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
- 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/eager-dispatch/admission.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -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 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -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 +15 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
- 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 +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
- package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
- package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
- package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
- 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 +77 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
- 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/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/computer-bridge/action-schema.mjs +44 -21
- package/src/runtime/github/tool.mjs +92 -2
- 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/pg/adapter.mjs +98 -1
- package/src/runtime/memory/lib/pg/process.mjs +65 -50
- package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
- 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 +2 -2
- package/src/runtime/office/authoring/html-document-author.mjs +2 -0
- package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
- 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 +98 -0
- package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
- package/src/runtime/office/index.mjs +30 -0
- 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/office/shared/values.mjs +0 -5
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/bounded-file-read.mjs +13 -0
- package/src/runtime/shared/bounded-line-diff.mjs +182 -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 +31 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- 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 +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- 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/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/loopback-listener.mjs +9 -0
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/provider-api-key.mjs +2 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/review-diff.mjs +52 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -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 +114 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/tool-card-model/card-header.mjs +6 -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-limit.mjs +4 -0
- package/src/runtime/shared/tool-primitives.mjs +13 -0
- package/src/runtime/shared/tool-result-summary.mjs +8 -1
- package/src/runtime/shared/tool-surface.mjs +18 -3
- package/src/runtime/shared/tool-work-units.mjs +40 -4
- package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
- package/src/runtime/shared/transcript-writer.mjs +10 -2
- package/src/runtime/shared/turn-changes.mjs +670 -274
- package/src/runtime/shared/turn-review-reply.mjs +8 -39
- package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
- package/src/runtime/shared/turn-snapshot.mjs +986 -690
- package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
- package/src/runtime/shared/user-data-guard.mjs +262 -49
- package/src/runtime/shared/worktrees/carry.mjs +531 -0
- package/src/runtime/shared/worktrees/contract.mjs +34 -24
- package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
- package/src/runtime/shared/worktrees/git-head.mjs +205 -0
- package/src/runtime/shared/worktrees/git.mjs +8 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
- package/src/runtime/shared/worktrees/manager.mjs +156 -8
- package/src/runtime/shared/worktrees/merge.mjs +31 -21
- package/src/runtime/shared/worktrees/paths.d.mts +6 -0
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/runtime/terminal-bridge/client.mjs +44 -11
- 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 +19 -5
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/infrastructure.mjs +12 -1
- package/src/session-runtime/boot/session.mjs +0 -1
- package/src/session-runtime/boot/tools.mjs +4 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
- package/src/session-runtime/internal-tool-executor.mjs +6 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
- package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
- package/src/session-runtime/lifecycle/teardown.mjs +73 -8
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/mcp-glue/connect.mjs +46 -14
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +174 -0
- package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
- package/src/session-runtime/model-route/set-route.mjs +85 -17
- package/src/session-runtime/model-route/web-search-route.mjs +1 -1
- package/src/session-runtime/native-web-search/routes.mjs +22 -3
- package/src/session-runtime/native-web-search-usage.mjs +51 -0
- package/src/session-runtime/native-web-search.mjs +10 -1
- 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-models/model-meta.mjs +12 -3
- package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
- package/src/session-runtime/quick-web-search-models.mjs +13 -38
- package/src/session-runtime/resource-extension-sync.mjs +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/resource-scope-decorate.mjs +10 -3
- package/src/session-runtime/runtime-review-api.mjs +21 -2
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
- 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.mjs +73 -7
- package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -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 +46 -7
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/services/agent-tool.mjs +3 -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 +81 -15
- package/src/session-runtime/session-transcript-purge.mjs +32 -0
- package/src/session-runtime/session-turn-api.mjs +14 -5
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/session-runtime/setup-tool/executor.mjs +36 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
- package/src/session-runtime/skills-api.mjs +3 -5
- package/src/session-runtime/tool-surface.mjs +5 -1
- 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 +22 -0
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +25 -9
- package/src/session-runtime/usage-stats-api.mjs +43 -11
- package/src/session-runtime/workspace/project-branches.mjs +7 -5
- package/src/session-runtime/workspace/transition-steps.mjs +129 -31
- package/src/session-runtime/workspace/transitions.mjs +190 -15
- package/src/standalone/daemon-agent-control.mjs +9 -1
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- 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 +51 -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 +2 -0
- package/src/standalone/daemon-telemetry.mjs +74 -0
- package/src/standalone/daemon.mjs +64 -8
- package/src/standalone/desktop-service-registry.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +70 -19
- 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 +1 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
- package/src/standalone/session-service/agent-tree.mjs +12 -0
- package/src/standalone/session-service/quota-resume.mjs +8 -0
- package/src/standalone/session-service/retention/busy.mjs +15 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
- package/src/standalone/session-service-api.mjs +11 -1
- package/src/standalone/session-service.mjs +1 -0
- package/src/standalone/session-state-patch.d.mts +2 -1
- package/src/standalone/session-state-patch.mjs +41 -1
- 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 +1 -0
- package/src/tui/app/btw-controller.mjs +118 -0
- package/src/tui/app/create-app-pickers.mjs +6 -0
- package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
- package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- 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 +20 -5
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/app/slash-dispatch/session-commands.mjs +19 -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/StatusLine.jsx +2 -0
- package/src/tui/components/question-panel-model.mjs +156 -0
- package/src/tui/components/status-line/local-line.mjs +5 -2
- package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
- package/src/tui/dist/index.mjs +3221 -1500
- package/src/tui/session/context-state.mjs +4 -2
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +43 -24
- package/src/tui/session/session-api/controls.mjs +2 -2
- package/src/tui/session/session-api/extensions.mjs +22 -0
- package/src/tui/session/session-api/integrations.mjs +16 -2
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +7 -2
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +11 -14
- package/src/tui/session/session-api.mjs +1 -0
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/tool-approval.mjs +10 -7
- package/src/tui/session/transcript-spill.mjs +14 -7
- package/src/tui/session/turn-stream.mjs +174 -31
- package/src/tui/session/turn.mjs +46 -16
- package/src/tui/statusline-ansi-bridge.mjs +1 -1
- package/src/ui/statusline-agents.mjs +15 -4
- package/src/ui/statusline.mjs +7 -3
- package/src/ui/streaming-markdown-heal.mjs +34 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
- package/src/workflows/default/WORKFLOW.md +3 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Persistent file-host journal scopes. No payload is read or deleted here.
|
|
2
|
+
* A retained record (even resolved) needs an explicit discard before its
|
|
3
|
+
* workspace can be removed. An unreadable/symlinked store is never empty. */
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { realpathSync } from 'node:fs';
|
|
6
|
+
import { lstat, opendir } from 'node:fs/promises';
|
|
7
|
+
import { basename, dirname, isAbsolute, resolve } from 'node:path';
|
|
8
|
+
import { worktreeError } from './contract.mjs';
|
|
9
|
+
import { isWithin } from './paths.mjs';
|
|
10
|
+
|
|
11
|
+
const fail = (record, root) => worktreeError('WORKTREE_FILE_RECOVERY', undefined, {
|
|
12
|
+
entryId: record.id, path: record.path, recoveryRoot: root,
|
|
13
|
+
});
|
|
14
|
+
export function validateRecoveryScope(record, scope) {
|
|
15
|
+
if (!scope || typeof scope.root !== 'string' || !isAbsolute(scope.root) ||
|
|
16
|
+
typeof scope.directory !== 'string' || !isAbsolute(scope.directory) ||
|
|
17
|
+
!isWithin(record.path, scope.root) || basename(dirname(scope.directory)) !== 'editor-recovery' ||
|
|
18
|
+
basename(scope.directory) !== createHash('sha256').update(resolve(scope.root)).digest('hex'))
|
|
19
|
+
throw fail(record, record.path);
|
|
20
|
+
return scope;
|
|
21
|
+
}
|
|
22
|
+
export function recoveryScopes(record) {
|
|
23
|
+
const scopes = record.editorRecoveryStores ?? [];
|
|
24
|
+
if (!Array.isArray(scopes) || scopes.length > 64) throw fail(record, record.path);
|
|
25
|
+
return scopes.map((scope) => validateRecoveryScope(record, scope));
|
|
26
|
+
}
|
|
27
|
+
async function recoveryStoreIsEmpty(scope) {
|
|
28
|
+
try {
|
|
29
|
+
// Check every existing ancestor of the private journal store; missing
|
|
30
|
+
// stores are permitted because registration deliberately precedes mkdir.
|
|
31
|
+
for (const directory of [dirname(dirname(scope.directory)), dirname(scope.directory), scope.directory]) {
|
|
32
|
+
let info;
|
|
33
|
+
try { info = await lstat(directory); }
|
|
34
|
+
catch (error) { if (error?.code === 'ENOENT') continue; throw error; }
|
|
35
|
+
// Use the same path spelling as the file host's store validation;
|
|
36
|
+
// async/native realpath can expand Windows short names differently.
|
|
37
|
+
if (!info.isDirectory() || info.isSymbolicLink() || realpathSync(directory) !== resolve(directory))
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
let directory;
|
|
41
|
+
try { directory = await opendir(scope.directory); }
|
|
42
|
+
catch (error) { if (error?.code === 'ENOENT') return true; throw error; }
|
|
43
|
+
for await (const entry of directory) {
|
|
44
|
+
// Also retain unknown/tampered entries, rather than guessing they are
|
|
45
|
+
// expendable. No manifest or phase can authorize deleting the workspace.
|
|
46
|
+
if (entry) return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Call under the repository lock. Only descriptors for verifiably empty or
|
|
55
|
+
// missing stores may be pruned; this never removes storage or backup bytes.
|
|
56
|
+
export async function retainedRecoveryScopes(record) {
|
|
57
|
+
const retained = [];
|
|
58
|
+
for (const scope of recoveryScopes(record)) {
|
|
59
|
+
if (!(await recoveryStoreIsEmpty(scope))) retained.push(scope);
|
|
60
|
+
}
|
|
61
|
+
return retained;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function assertRecoveryStoresClear(record) {
|
|
65
|
+
for (const scope of recoveryScopes(record)) {
|
|
66
|
+
if (!(await recoveryStoreIsEmpty(scope))) throw fail(record, scope.root);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn-free read of a checkout's HEAD: the repository layout (work tree root,
|
|
3
|
+
* git dir, common dir) is found by walking up from a folder to its `.git`
|
|
4
|
+
* (a directory, or a `gitdir:` file for linked worktrees and submodules), and
|
|
5
|
+
* HEAD is the one small file in the git dir. Packed refs never matter: HEAD
|
|
6
|
+
* names a branch (`ref: refs/heads/x`) or holds a commit id.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, statSync, watch } from 'node:fs';
|
|
9
|
+
import { dirname, resolve } from 'node:path';
|
|
10
|
+
import { canonicalPath } from './paths.mjs';
|
|
11
|
+
|
|
12
|
+
const OBJECT_ID = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i;
|
|
13
|
+
const NOT_A_REPO_TTL_MS = 1000;
|
|
14
|
+
|
|
15
|
+
/** start dir → { topLevel, gitDir, commonDir } for folders inside a checkout. */
|
|
16
|
+
const layouts = new Map();
|
|
17
|
+
/** start dir → time a walk found no repository. */
|
|
18
|
+
const notRepos = new Map();
|
|
19
|
+
/** git dir → last HEAD read, kept across a half-written HEAD. */
|
|
20
|
+
const lastHeads = new Map();
|
|
21
|
+
|
|
22
|
+
function readText(path) {
|
|
23
|
+
try {
|
|
24
|
+
return readFileSync(path, 'utf8');
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isDirectory(path) {
|
|
31
|
+
try {
|
|
32
|
+
return statSync(path).isDirectory();
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isFile(path) {
|
|
39
|
+
try {
|
|
40
|
+
return statSync(path).isFile();
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function layoutAbove(start) {
|
|
47
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
48
|
+
const dotGit = resolve(dir, '.git');
|
|
49
|
+
let gitDir = null;
|
|
50
|
+
if (isDirectory(dotGit)) {
|
|
51
|
+
gitDir = dotGit;
|
|
52
|
+
} else if (isFile(dotGit)) {
|
|
53
|
+
const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(readText(dotGit) ?? '');
|
|
54
|
+
if (pointer) gitDir = resolve(dir, pointer[1]);
|
|
55
|
+
}
|
|
56
|
+
if (gitDir && isFile(resolve(gitDir, 'HEAD'))) {
|
|
57
|
+
const common = readText(resolve(gitDir, 'commondir'))?.trim();
|
|
58
|
+
return {
|
|
59
|
+
topLevel: canonicalPath(dir),
|
|
60
|
+
gitDir,
|
|
61
|
+
commonDir: canonicalPath(common ? resolve(gitDir, common) : gitDir),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (dirname(dir) === dir) return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The checkout layout containing `cwd`, or null outside a repository. */
|
|
69
|
+
export function findGitLayout(cwd) {
|
|
70
|
+
const start = resolve(String(cwd || '.'));
|
|
71
|
+
const known = layouts.get(start);
|
|
72
|
+
if (known && isFile(resolve(known.gitDir, 'HEAD'))) return known;
|
|
73
|
+
layouts.delete(start);
|
|
74
|
+
const missedAt = notRepos.get(start);
|
|
75
|
+
if (missedAt !== undefined && Date.now() - missedAt < NOT_A_REPO_TTL_MS) return null;
|
|
76
|
+
const found = layoutAbove(start);
|
|
77
|
+
if (!found) {
|
|
78
|
+
notRepos.set(start, Date.now());
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
notRepos.delete(start);
|
|
82
|
+
layouts.set(start, found);
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const MAX_SYMREF_DEPTH = 5;
|
|
87
|
+
const PER_WORKTREE_REFS = /^refs\/(?:bisect|worktree|rewritten)\//;
|
|
88
|
+
|
|
89
|
+
/** Where a ref's loose file lives: per-worktree refs in the git dir, the rest in the common dir. */
|
|
90
|
+
function refFile(layout, name) {
|
|
91
|
+
return resolve(PER_WORKTREE_REFS.test(name) ? layout.gitDir : layout.commonDir, name);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Follows HEAD's symbolic refs like git does (packed-refs never holds a
|
|
96
|
+
* symref, so only loose ref files are read). Returns `{ name, files }`: the
|
|
97
|
+
* ref HEAD finally resolves to (the first non-symbolic ref, or the first
|
|
98
|
+
* missing target: an unborn branch) and every file read along the chain, or
|
|
99
|
+
* `{ name: null, files }` for a cycle or a chain over the depth limit.
|
|
100
|
+
*/
|
|
101
|
+
function followHead(layout, text) {
|
|
102
|
+
const files = [resolve(layout.gitDir, 'HEAD')];
|
|
103
|
+
let name = /^ref:\s*(.+)$/.exec(text)?.[1].trim();
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
for (let depth = 0; name && depth < MAX_SYMREF_DEPTH; depth++) {
|
|
106
|
+
if (seen.has(name)) return { name: null, files };
|
|
107
|
+
seen.add(name);
|
|
108
|
+
const file = refFile(layout, name);
|
|
109
|
+
files.push(file);
|
|
110
|
+
const next = /^ref:\s*(.+)$/.exec(readText(file)?.trim() ?? '')?.[1].trim();
|
|
111
|
+
if (!next) return { name, files };
|
|
112
|
+
name = next;
|
|
113
|
+
}
|
|
114
|
+
return { name: null, files };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Files whose change can change what `gitDir`'s HEAD resolves to. */
|
|
118
|
+
function headChainFiles(layout) {
|
|
119
|
+
const text = readText(resolve(layout.gitDir, 'HEAD'))?.trim();
|
|
120
|
+
return text && /^ref:/.test(text) ? followHead(layout, text).files : [resolve(layout.gitDir, 'HEAD')];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* What `cwd`'s checkout has checked out: `{ topLevel, gitDir, repoCommonDir,
|
|
125
|
+
* branch, commit }` (`branch` null when detached or when the symbolic-ref
|
|
126
|
+
* chain is broken, `commit` null on a branch), or null when `cwd` is not
|
|
127
|
+
* inside a git repository. A branch alias resolves to the branch it points at.
|
|
128
|
+
*/
|
|
129
|
+
export function readGitHead(cwd) {
|
|
130
|
+
const layout = findGitLayout(cwd);
|
|
131
|
+
if (!layout) return null;
|
|
132
|
+
const text = readText(resolve(layout.gitDir, 'HEAD'))?.trim();
|
|
133
|
+
let head = lastHeads.get(layout.gitDir) ?? { branch: null, commit: null };
|
|
134
|
+
if (text && /^ref:\s*(.+)$/.test(text)) {
|
|
135
|
+
const { name } = followHead(layout, text);
|
|
136
|
+
head = { branch: name?.startsWith('refs/heads/') ? name.slice('refs/heads/'.length) : null, commit: null };
|
|
137
|
+
} else if (text && OBJECT_ID.test(text)) {
|
|
138
|
+
head = { branch: null, commit: text.toLowerCase() };
|
|
139
|
+
}
|
|
140
|
+
lastHeads.set(layout.gitDir, head);
|
|
141
|
+
return {
|
|
142
|
+
topLevel: layout.topLevel,
|
|
143
|
+
gitDir: layout.gitDir,
|
|
144
|
+
repoCommonDir: layout.commonDir,
|
|
145
|
+
branch: head.branch,
|
|
146
|
+
commit: head.commit,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Calls `onChange` shortly after the HEAD of the checkout `getCwd()` names
|
|
152
|
+
* changes (a `git switch` from any terminal). Call `retarget()` whenever the
|
|
153
|
+
* folder may have changed; `dispose()` ends the watch.
|
|
154
|
+
*/
|
|
155
|
+
export function createGitHeadWatcher({ getCwd, onChange, debounceMs = 40 }) {
|
|
156
|
+
let target = '';
|
|
157
|
+
let watchers = [];
|
|
158
|
+
let timer = null;
|
|
159
|
+
const closeWatchers = () => {
|
|
160
|
+
for (const watcher of watchers) watcher.close();
|
|
161
|
+
watchers = [];
|
|
162
|
+
};
|
|
163
|
+
const notify = () => {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
timer = setTimeout(onChange, debounceMs);
|
|
166
|
+
timer.unref?.();
|
|
167
|
+
};
|
|
168
|
+
const retarget = () => {
|
|
169
|
+
const layout = findGitLayout(getCwd());
|
|
170
|
+
// HEAD and every symbolic ref it passes through: a change anywhere in
|
|
171
|
+
// that chain can change the branch.
|
|
172
|
+
const files = layout ? headChainFiles(layout) : [];
|
|
173
|
+
const key = files.join('\n');
|
|
174
|
+
if (key === target) return;
|
|
175
|
+
closeWatchers();
|
|
176
|
+
target = key;
|
|
177
|
+
// The directories, not the files: git replaces a ref by renaming <ref>.lock.
|
|
178
|
+
const dirs = [...new Set(files.map((file) => dirname(file)))];
|
|
179
|
+
for (const dir of dirs) {
|
|
180
|
+
try {
|
|
181
|
+
const watcher = watch(dir, { persistent: false }, (_event, name) => {
|
|
182
|
+
if (name && !files.includes(resolve(dir, String(name)))) return;
|
|
183
|
+
retarget();
|
|
184
|
+
notify();
|
|
185
|
+
});
|
|
186
|
+
watcher.on('error', () => {
|
|
187
|
+
watcher.close();
|
|
188
|
+
watchers = watchers.filter((w) => w !== watcher);
|
|
189
|
+
target = '';
|
|
190
|
+
});
|
|
191
|
+
watchers.push(watcher);
|
|
192
|
+
} catch {
|
|
193
|
+
// A chain directory that does not exist (an unborn target) is not watched.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
retarget,
|
|
199
|
+
dispose() {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
closeWatchers();
|
|
202
|
+
target = '';
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -68,14 +68,19 @@ const READ_ONLY_FLAGS = ['--no-optional-locks', '-c', 'core.fsmonitor=false'];
|
|
|
68
68
|
/**
|
|
69
69
|
* @param {string} cwd
|
|
70
70
|
* @param {string[]} args
|
|
71
|
-
* @param {{ signal?: AbortSignal, timeoutMs?: number, maxBytes?: number, allowFailure?: boolean, readOnly?: boolean }} [options]
|
|
71
|
+
* @param {{ signal?: AbortSignal, timeoutMs?: number, maxBytes?: number, allowFailure?: boolean, readOnly?: boolean, env?: Record<string, string> }} [options]
|
|
72
|
+
* `env`: variables set on top of the pinned environment (a temporary GIT_INDEX_FILE, a commit identity).
|
|
72
73
|
* @returns {Promise<{ code: number, stdout: string, stderr: string }>}
|
|
73
74
|
*/
|
|
74
|
-
export async function runGit(
|
|
75
|
+
export async function runGit(
|
|
76
|
+
cwd,
|
|
77
|
+
args,
|
|
78
|
+
{ signal, timeoutMs, maxBytes, allowFailure = false, readOnly = false, env = null } = {}
|
|
79
|
+
) {
|
|
75
80
|
throwIfAborted(signal);
|
|
76
81
|
const result = await runGitOffThread([...(readOnly ? READ_ONLY_FLAGS : []), ...SCOPED_CONFIG, ...args], {
|
|
77
82
|
cwd,
|
|
78
|
-
env: gitEnvironment(),
|
|
83
|
+
env: env ? { ...gitEnvironment(), ...env } : gitEnvironment(),
|
|
79
84
|
timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
80
85
|
maxBytes: maxBytes ?? DEFAULT_MAX_BYTES,
|
|
81
86
|
});
|
|
@@ -88,7 +88,7 @@ export async function cleanupDeletedSessionWorktree(sessionId, { manager = null,
|
|
|
88
88
|
try {
|
|
89
89
|
return { entryId: entry.id, ...(await backend.removeManagedWorktree({ entryId: entry.id })) };
|
|
90
90
|
} catch (error) {
|
|
91
|
-
if (isWorktreeError(error, 'WORKTREE_DIRTY') || isWorktreeError(error, 'WORKTREE_UNMERGED')) {
|
|
91
|
+
if (isWorktreeError(error, 'WORKTREE_DIRTY') || isWorktreeError(error, 'WORKTREE_UNMERGED') || isWorktreeError(error, 'WORKTREE_FILE_RECOVERY')) {
|
|
92
92
|
return { entryId: entry.id, removed: false, reason: error.code };
|
|
93
93
|
}
|
|
94
94
|
throw error;
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
* -d` forgets it).
|
|
27
27
|
*/
|
|
28
28
|
import { existsSync } from 'node:fs';
|
|
29
|
+
import { assertRecoveryStoresClear, recoveryScopes, retainedRecoveryScopes, validateRecoveryScope } from './file-recovery.mjs';
|
|
29
30
|
import { lstat, mkdir, readdir, readFile, rm, rmdir } from 'node:fs/promises';
|
|
30
|
-
import { dirname, join, relative, sep } from 'node:path';
|
|
31
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
31
32
|
import {
|
|
32
33
|
managedWorktreePath,
|
|
33
34
|
managedWorktreeRoot,
|
|
@@ -37,6 +38,13 @@ import {
|
|
|
37
38
|
worktreeBranchName,
|
|
38
39
|
worktreeError,
|
|
39
40
|
} from './contract.mjs';
|
|
41
|
+
import {
|
|
42
|
+
assertCarryable,
|
|
43
|
+
carryUncommittedChanges,
|
|
44
|
+
dropRecoveryRef,
|
|
45
|
+
restoreFromRecoveryRef,
|
|
46
|
+
uncommittedChanges,
|
|
47
|
+
} from './carry.mjs';
|
|
40
48
|
import { freeBytesAt, requiredBytes, trackedCheckoutBytes } from './disk.mjs';
|
|
41
49
|
import {
|
|
42
50
|
branchCommit,
|
|
@@ -64,7 +72,7 @@ import { withRepoLock as withRepoMutationLock } from './lock.mjs';
|
|
|
64
72
|
import { mergeBranchIntoCheckout } from './merge.mjs';
|
|
65
73
|
import { readOwnerLease, removeOwnerLease, sameOwner, writeOwnerLease } from './owner.mjs';
|
|
66
74
|
import { canonicalPath, checkoutFolder, isWithin, pathKey, projectSubdirOf, samePath } from './paths.mjs';
|
|
67
|
-
import { deleteRecord, entryView, getRecord, insertRecord, putRecord, readRegistry } from './registry.mjs';
|
|
75
|
+
import { deleteRecord, entryView, getRecord, insertRecord, putRecord, readRegistry, registryPath } from './registry.mjs';
|
|
68
76
|
import { normalizeWorktreeSettings } from './settings.mjs';
|
|
69
77
|
import { slugFromHint, uniqueSlug } from './slug.mjs';
|
|
70
78
|
|
|
@@ -74,6 +82,8 @@ const REMOVE_RETRY_DELAY_MS = 150;
|
|
|
74
82
|
// The session copies the project identity into each managed worktree; it is
|
|
75
83
|
// Mixdog metadata, not user data, so it alone never makes a worktree dirty.
|
|
76
84
|
const SESSION_PROJECT_ID_PATH = '.mixdog/project.id';
|
|
85
|
+
// Changed paths WORKTREE_DIRTY_CHOICE lists for the dialog's preview (the count is exact).
|
|
86
|
+
const DIRTY_CHOICE_PREVIEW = 50;
|
|
77
87
|
|
|
78
88
|
export { registerRootReleaser } from './contract.mjs';
|
|
79
89
|
|
|
@@ -327,6 +337,7 @@ async function deleteBranchIfFree(record) {
|
|
|
327
337
|
* still present (empty when everything is gone).
|
|
328
338
|
*/
|
|
329
339
|
async function removeCheckout(record) {
|
|
340
|
+
await assertRecoveryStoresClear(record);
|
|
330
341
|
await assertManagedCheckoutPath(record);
|
|
331
342
|
const remaining = [];
|
|
332
343
|
for (const path of await removeIncludeLinks(record.resources?.includes)) remaining.push({ kind: 'link', path });
|
|
@@ -437,6 +448,7 @@ async function rollbackCreation(record, cause) {
|
|
|
437
448
|
* and retried with the deletion; it never keeps the checkout.
|
|
438
449
|
*/
|
|
439
450
|
async function finishDeletion(record) {
|
|
451
|
+
await assertRecoveryStoresClear(record);
|
|
440
452
|
const remaining = [];
|
|
441
453
|
try {
|
|
442
454
|
await removeOwnerLease(record);
|
|
@@ -516,6 +528,46 @@ export async function findWorktreeContaining(path) {
|
|
|
516
528
|
return record ? entryView(record) : null;
|
|
517
529
|
}
|
|
518
530
|
|
|
531
|
+
/** File-host seam, not a user capability. Persist the host-owned scope before
|
|
532
|
+
* preparing a byte journal, and hold the same repository lock as every removal
|
|
533
|
+
* through the operation. The descriptor survives daemon exit/startup reconcile.
|
|
534
|
+
* @template T
|
|
535
|
+
* @param {string} root
|
|
536
|
+
* @param {string} directory
|
|
537
|
+
* @param {'write'|'restore'|'inspect'|'discard'} action
|
|
538
|
+
* @param {() => Promise<T>} run
|
|
539
|
+
* @returns {Promise<T>}
|
|
540
|
+
*/
|
|
541
|
+
export async function withWorktreeRecoveryStore(root, directory, action, run) {
|
|
542
|
+
const entry = await findWorktreeContaining(root);
|
|
543
|
+
if (!entry) {
|
|
544
|
+
// A managed checkout can disappear between host authorization and this
|
|
545
|
+
// lookup. Its reserved namespace must never become an ordinary project.
|
|
546
|
+
// Retain a lexical check as well, so a replacement link cannot escape it.
|
|
547
|
+
const store = dirname(registryPath());
|
|
548
|
+
const lexicalKey = (path) => process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path);
|
|
549
|
+
const requested = lexicalKey(root);
|
|
550
|
+
if (isWithin(store, root) || [store, canonicalPath(store)].some((path) =>
|
|
551
|
+
requested === lexicalKey(path) || requested.startsWith(lexicalKey(path) + sep)))
|
|
552
|
+
throw worktreeError('WORKTREE_MISSING', undefined, { path: root });
|
|
553
|
+
return run();
|
|
554
|
+
}
|
|
555
|
+
return withRepoLock(entry.repoCommonDir, async () => {
|
|
556
|
+
const record = await getRecord(entry.id);
|
|
557
|
+
if (!record || ((action === 'write' || action === 'restore') && record.state !== 'ready'))
|
|
558
|
+
throw worktreeError('WORKTREE_MISSING', undefined, { entryId: entry.id });
|
|
559
|
+
const scope = validateRecoveryScope(record, { root, directory });
|
|
560
|
+
let scopes = recoveryScopes(record);
|
|
561
|
+
if (!scopes.some((item) => item.root === root && item.directory === directory)) {
|
|
562
|
+
if (scopes.length >= 64) scopes = await retainedRecoveryScopes(record);
|
|
563
|
+
if (scopes.length >= 64) throw worktreeError('WORKTREE_FILE_RECOVERY');
|
|
564
|
+
record.editorRecoveryStores = [...scopes, scope];
|
|
565
|
+
await putRecord(record);
|
|
566
|
+
}
|
|
567
|
+
return run();
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
519
571
|
/** Mark an entry disposable: startup reconcile may remove it once clean and merged. */
|
|
520
572
|
export async function markWorktreeDisposable(entryId, disposable = true) {
|
|
521
573
|
const record = await getRecord(entryId);
|
|
@@ -600,8 +652,20 @@ async function diskFullFailure(error, { needed, root }) {
|
|
|
600
652
|
}
|
|
601
653
|
|
|
602
654
|
/**
|
|
655
|
+
* `carryChanges` decides the project folder's uncommitted changes (tracked
|
|
656
|
+
* and untracked, ignored excluded), judged under the repository lock:
|
|
657
|
+
* omitted/null leaves them unexamined; `ask` refuses with
|
|
658
|
+
* WORKTREE_DIRTY_CHOICE (`details: { count, paths }`, paths capped) while
|
|
659
|
+
* there are any; `leave` keeps them in the project folder; `carry` moves
|
|
660
|
+
* them into the new worktree (carry.mjs: whole checkout, ignored files stay)
|
|
661
|
+
* — refused (WORKTREE_DIRTY) during a merge, rebase or cherry-pick, with
|
|
662
|
+
* unmerged entries, dirty submodules, filtered (LFS) paths or oversized
|
|
663
|
+
* untracked files. The result's `changes` (`{ mode: 'carried'|'left',
|
|
664
|
+
* count, recoveryRef? }`) reports what happened to them; a carried move's
|
|
665
|
+
* `recoveryRef` stays until dropCarryRecoveryRef / restoreCarriedChanges.
|
|
603
666
|
* @param {{ projectPath: string, sessionId: string, runtimeToken: string, baseBranch?: string|null,
|
|
604
|
-
* nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal
|
|
667
|
+
* nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal,
|
|
668
|
+
* carryChanges?: 'ask'|'leave'|'carry'|null }} input
|
|
605
669
|
*/
|
|
606
670
|
export async function createManagedWorktree({
|
|
607
671
|
projectPath,
|
|
@@ -612,9 +676,13 @@ export async function createManagedWorktree({
|
|
|
612
676
|
confirmLarge = false,
|
|
613
677
|
txId,
|
|
614
678
|
signal,
|
|
679
|
+
carryChanges = null,
|
|
615
680
|
} = {}) {
|
|
616
681
|
requireText(projectPath, 'projectPath');
|
|
617
682
|
requireText(txId, 'txId');
|
|
683
|
+
if (![null, 'ask', 'leave', 'carry'].includes(carryChanges)) {
|
|
684
|
+
throw new TypeError('carryChanges must be ask, leave or carry.');
|
|
685
|
+
}
|
|
618
686
|
const owner = normalizeWorktreeOwner({ sessionId, runtimeToken, pid: process.pid });
|
|
619
687
|
throwIfAborted(signal);
|
|
620
688
|
const repo = await inspectRepo(projectPath, { signal });
|
|
@@ -623,6 +691,18 @@ export async function createManagedWorktree({
|
|
|
623
691
|
if (existing) return existing;
|
|
624
692
|
throwIfAborted(signal);
|
|
625
693
|
|
|
694
|
+
const projectSubdir = projectSubdirOf(repo.topLevel, repo.projectPath);
|
|
695
|
+
// A carry is judged by the same status read that counts the changes.
|
|
696
|
+
let pendingChanges = [];
|
|
697
|
+
if (carryChanges === 'carry') pendingChanges = await assertCarryable(repo.topLevel, projectSubdir);
|
|
698
|
+
else if (carryChanges) pendingChanges = await uncommittedChanges(repo.topLevel, projectSubdir);
|
|
699
|
+
if (pendingChanges.length && carryChanges === 'ask') {
|
|
700
|
+
throw worktreeError('WORKTREE_DIRTY_CHOICE', undefined, {
|
|
701
|
+
count: pendingChanges.length,
|
|
702
|
+
paths: pendingChanges.slice(0, DIRTY_CHOICE_PREVIEW),
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
626
706
|
const settings = await currentWorktreeSettings();
|
|
627
707
|
let baseCommit = repo.headCommit;
|
|
628
708
|
let mergeTarget = repo.currentBranch;
|
|
@@ -675,6 +755,7 @@ export async function createManagedWorktree({
|
|
|
675
755
|
timeoutMs: WORKTREE_ADD_TIMEOUT_MS,
|
|
676
756
|
});
|
|
677
757
|
let record = await reserve();
|
|
758
|
+
let carried = null;
|
|
678
759
|
|
|
679
760
|
try {
|
|
680
761
|
await mkdir(root, { recursive: true });
|
|
@@ -708,16 +789,46 @@ export async function createManagedWorktree({
|
|
|
708
789
|
await setResource(record, 'owner', 'pending');
|
|
709
790
|
await writeOwnerLease(record, owner);
|
|
710
791
|
await setResource(record, 'owner', 'created');
|
|
792
|
+
// Carried before the include entries are copied, so an include never
|
|
793
|
+
// stands in the way of a carried file; the status is read again here,
|
|
794
|
+
// right before acting.
|
|
795
|
+
let changes = pendingChanges.length ? { mode: 'left', count: pendingChanges.length } : null;
|
|
796
|
+
if (carryChanges === 'carry') {
|
|
797
|
+
carried = await carryUncommittedChanges({
|
|
798
|
+
topLevel: repo.topLevel,
|
|
799
|
+
projectSubdir,
|
|
800
|
+
worktreePath: record.path,
|
|
801
|
+
baseCommit,
|
|
802
|
+
label: record.id,
|
|
803
|
+
});
|
|
804
|
+
// The recovery ref stays until the caller has committed the move
|
|
805
|
+
// (dropCarryRecoveryRef) or put the changes back (restoreCarriedChanges).
|
|
806
|
+
changes = carried.count ? { mode: 'carried', count: carried.count, recoveryRef: carried.recoveryRef } : null;
|
|
807
|
+
}
|
|
711
808
|
const applied = await applyIncludes(includePlan, record.path, (ledger) => recordInclude(record, ledger));
|
|
712
809
|
throwIfAborted(signal);
|
|
713
810
|
record.warnings = [...record.warnings, ...applied.warnings];
|
|
714
811
|
record.state = 'ready';
|
|
715
812
|
await putRecord(record);
|
|
716
|
-
return entryView(record);
|
|
813
|
+
return { ...entryView(record), ...(changes ? { changes } : {}) };
|
|
717
814
|
} catch (error) {
|
|
815
|
+
// Carried already: the changes go back to the project folder before the
|
|
816
|
+
// worktree that holds them is rolled back (the recovery ref keeps them
|
|
817
|
+
// either way).
|
|
818
|
+
let carryRestore = null;
|
|
819
|
+
if (carried?.recoveryRef) {
|
|
820
|
+
carryRestore = await restoreFromRecoveryRef(repo.topLevel, projectSubdir, carried.recoveryRef)
|
|
821
|
+
.then(() => ({ restored: true, recoveryRef: carried.recoveryRef }))
|
|
822
|
+
.catch((restoreError) => ({
|
|
823
|
+
restored: false,
|
|
824
|
+
recoveryRef: carried.recoveryRef,
|
|
825
|
+
error: String(restoreError?.message || restoreError),
|
|
826
|
+
}));
|
|
827
|
+
}
|
|
718
828
|
const rollback = await rollbackCreation(record, error);
|
|
719
829
|
const failure = isDiskFullError(error) ? await diskFullFailure(error, { needed, root }) : error;
|
|
720
830
|
if (!rollback.complete) failure.rollback = rollback;
|
|
831
|
+
if (carryRestore) failure.carryRestore = carryRestore;
|
|
721
832
|
throw failure;
|
|
722
833
|
}
|
|
723
834
|
|
|
@@ -758,6 +869,30 @@ export async function createManagedWorktree({
|
|
|
758
869
|
});
|
|
759
870
|
}
|
|
760
871
|
|
|
872
|
+
/**
|
|
873
|
+
* After a carry whose session move failed: put the changes kept at
|
|
874
|
+
* `recoveryRef` back into the project folder (refused, ref kept, when the
|
|
875
|
+
* folder changed meanwhile). The ref stays; drop it with dropCarryRecoveryRef.
|
|
876
|
+
* @param {{ projectPath: string, recoveryRef: string }} input
|
|
877
|
+
*/
|
|
878
|
+
export async function restoreCarriedChanges({ projectPath, recoveryRef } = {}) {
|
|
879
|
+
requireText(projectPath, 'projectPath');
|
|
880
|
+
const repo = await inspectRepo(projectPath);
|
|
881
|
+
return withRepoLock(repo.repoCommonDir, () =>
|
|
882
|
+
restoreFromRecoveryRef(repo.topLevel, projectSubdirOf(repo.topLevel, repo.projectPath), recoveryRef)
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Forget a carry's recovery ref once the session move it protected is committed.
|
|
888
|
+
* @param {{ projectPath: string, recoveryRef: string }} input
|
|
889
|
+
*/
|
|
890
|
+
export async function dropCarryRecoveryRef({ projectPath, recoveryRef } = {}) {
|
|
891
|
+
requireText(projectPath, 'projectPath');
|
|
892
|
+
const repo = await inspectRepo(projectPath);
|
|
893
|
+
return withRepoLock(repo.repoCommonDir, () => dropRecoveryRef(repo.topLevel, recoveryRef));
|
|
894
|
+
}
|
|
895
|
+
|
|
761
896
|
/**
|
|
762
897
|
* Claim the live lease of a retained worktree for one runtime of its session.
|
|
763
898
|
* @param {string} entryId
|
|
@@ -1054,12 +1189,14 @@ async function assertRemovable(record) {
|
|
|
1054
1189
|
/**
|
|
1055
1190
|
* The refusal removeManagedWorktree would raise, checked before anything
|
|
1056
1191
|
* moves (a session leaves its worktree only for a removal that can proceed).
|
|
1057
|
-
* Unknown entries are WORKTREE_MISSING
|
|
1192
|
+
* Unknown entries are WORKTREE_MISSING. Recovery records block even `force`
|
|
1193
|
+
* and non-ready entries; ordinary dirty/unmerged checks apply only to ready ones.
|
|
1058
1194
|
* @param {{ entryId: string, force?: boolean }} input
|
|
1059
1195
|
*/
|
|
1060
1196
|
export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
|
|
1061
1197
|
const record = await getRecord(entryId);
|
|
1062
1198
|
if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
1199
|
+
await assertRecoveryStoresClear(record);
|
|
1063
1200
|
if (record.state !== 'ready') return;
|
|
1064
1201
|
await assertManagedCheckoutPath(record);
|
|
1065
1202
|
if (!force) await assertRemovable(record);
|
|
@@ -1067,7 +1204,8 @@ export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
|
|
|
1067
1204
|
|
|
1068
1205
|
/**
|
|
1069
1206
|
* Remove a managed worktree. Refuses dirty (incl. untracked and modified
|
|
1070
|
-
* include copies) or unmerged worktrees unless `force`.
|
|
1207
|
+
* include copies) or unmerged worktrees unless `force`. Retained file recovery
|
|
1208
|
+
* data blocks removal regardless of force.
|
|
1071
1209
|
* @param {{ entryId: string, force?: boolean }} input
|
|
1072
1210
|
* @returns {Promise<{ removed: boolean, retryable?: boolean, remaining?: object[], branchDeleted?: boolean }>}
|
|
1073
1211
|
*/
|
|
@@ -1082,6 +1220,7 @@ export async function removeManagedWorktree({ entryId, force = false } = {}) {
|
|
|
1082
1220
|
return rollback.complete ? { removed: true } : { removed: false, retryable: true, remaining: rollback.remaining };
|
|
1083
1221
|
}
|
|
1084
1222
|
if (current.state === 'ready') {
|
|
1223
|
+
await assertRecoveryStoresClear(current);
|
|
1085
1224
|
await assertManagedCheckoutPath(current);
|
|
1086
1225
|
if (!force) await assertRemovable(current);
|
|
1087
1226
|
current.state = 'deleting';
|
|
@@ -1101,7 +1240,7 @@ function leaseProcessAlive(lease) {
|
|
|
1101
1240
|
* Every managed worktree with who keeps it: `retained` (its session is in
|
|
1102
1241
|
* `retainedSessionIds`, i.e. still in the session store), `ownerLive` (a
|
|
1103
1242
|
* live session or a running lease holder), and `removable` (what a prune
|
|
1104
|
-
* would remove: neither, or disposable and not live).
|
|
1243
|
+
* would remove: neither, or disposable and not live, with no retained recovery data).
|
|
1105
1244
|
* @param {{ liveSessionIds?: string[], retainedSessionIds?: string[] }} input
|
|
1106
1245
|
*/
|
|
1107
1246
|
export async function listManagedWorktrees({ liveSessionIds = [], retainedSessionIds = [] } = {}) {
|
|
@@ -1112,12 +1251,20 @@ export async function listManagedWorktrees({ liveSessionIds = [], retainedSessio
|
|
|
1112
1251
|
const ownerLive =
|
|
1113
1252
|
(!!record.ownerSessionId && live.has(record.ownerSessionId)) || leaseProcessAlive(await readOwnerLease(record));
|
|
1114
1253
|
const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
|
|
1254
|
+
let removable = record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained));
|
|
1255
|
+
if (removable) {
|
|
1256
|
+
try { await assertRecoveryStoresClear(record); }
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
if (error?.code !== 'WORKTREE_FILE_RECOVERY') throw error;
|
|
1259
|
+
removable = false;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1115
1262
|
rows.push({
|
|
1116
1263
|
...entryView(record),
|
|
1117
1264
|
projectPath: record.projectPath,
|
|
1118
1265
|
retained: isRetained,
|
|
1119
1266
|
ownerLive,
|
|
1120
|
-
removable
|
|
1267
|
+
removable,
|
|
1121
1268
|
});
|
|
1122
1269
|
}
|
|
1123
1270
|
return rows;
|
|
@@ -1194,6 +1341,7 @@ export async function reconcileManagedWorktrees({
|
|
|
1194
1341
|
report.kept.push({ id: record.id, reason: dirty.length ? 'dirty' : 'unmerged' });
|
|
1195
1342
|
continue;
|
|
1196
1343
|
}
|
|
1344
|
+
await assertRecoveryStoresClear(record);
|
|
1197
1345
|
record.state = 'deleting';
|
|
1198
1346
|
record.deleting = { force: false, startedAt: Date.now(), remaining: [] };
|
|
1199
1347
|
await putRecord(record);
|