mixdog 1.2.3 → 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/package.json +1 -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 +128 -38
- package/scripts/session-sweep.mjs +5 -25
- 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 +6 -1
- package/src/defaults/skills/image/SKILL.md +21 -23
- package/src/lib/keychain-cjs.cjs +13 -11
- package/src/rules/agent/AGENT.md +7 -5
- 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/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-error-contract.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
- 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 +2 -5
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -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 +81 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
- 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 +41 -11
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
- 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 +6 -1
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
- package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
- 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/terminal-frames.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
- package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
- 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/skill-tool-loading.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
- 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 +3 -11
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- 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 +1 -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 +2 -4
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -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 +4 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
- 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 +21 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
- 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 +29 -8
- 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 +32 -14
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
- package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
- 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/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 +5 -11
- 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 -32
- 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 +1 -1
- 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-catalog.mjs +50 -116
- 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 +7 -8
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
- package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
- package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
- 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/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/external-tool-adapters.mjs +204 -10
- 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 +200 -17
- 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/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-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/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 +178 -178
- 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 +118 -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/lib/ingest/transcript-rows.mjs +1 -0
- package/src/runtime/memory/lib/pg/process.mjs +5 -7
- package/src/runtime/memory/lib/pg/supervisor.mjs +4 -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/pdf-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
- package/src/runtime/office/core/office-write-ledger.mjs +1 -5
- package/src/runtime/office/index.mjs +2 -3
- package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
- 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-line-diff.mjs +11 -2
- package/src/runtime/shared/browser-launch.mjs +15 -0
- package/src/runtime/shared/err-text.mjs +42 -4
- package/src/runtime/shared/file-lock.mjs +6 -5
- package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
- package/src/runtime/shared/llm/provider-model-identities.mjs +1 -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 +6 -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/slow-span.mjs +8 -1
- package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
- package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
- package/src/runtime/shared/tool-output-budget.mjs +2 -0
- package/src/runtime/shared/tool-result-summary.mjs +16 -2
- package/src/runtime/shared/tool-status.mjs +3 -2
- package/src/runtime/shared/tool-surface.mjs +2 -3
- package/src/runtime/shared/tool-work-units.mjs +22 -15
- 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/transcript-file.mjs +10 -1
- package/src/runtime/shared/transcript-writer.mjs +14 -4
- package/src/runtime/shared/turn-changes.mjs +2 -1
- package/src/runtime/shared/turn-snapshot.mjs +11 -3
- 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 +6 -4
- package/src/runtime/shared/worktrees/contract.mjs +27 -1
- package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
- package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
- package/src/runtime/shared/worktrees/git-head.mjs +22 -13
- 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 +378 -32
- package/src/runtime/shared/worktrees/owner.mjs +2 -1
- 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 +14 -10
- package/src/session-runtime/boot/apis.mjs +7 -1
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +3 -0
- package/src/session-runtime/boot/session.mjs +3 -2
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/conversation-import/service.mjs +1 -1
- package/src/session-runtime/goal-facade-api.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
- 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 +7 -16
- 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 +114 -34
- 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.mjs +9 -8
- 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/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/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/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/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 +9 -6
- 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 +3 -9
- 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.mjs +28 -8
- package/src/session-runtime/services/agent-tool.mjs +4 -0
- package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
- package/src/session-runtime/services/provider-admin.mjs +77 -0
- 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.mjs +4 -5
- package/src/session-runtime/session-turn-api.mjs +3 -9
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
- package/src/session-runtime/setup-tool/executor.mjs +23 -5
- package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
- package/src/session-runtime/tool-policy-refresh.mjs +83 -32
- package/src/session-runtime/turn/agent-ops.mjs +49 -2
- package/src/session-runtime/turn/side-question-ops.mjs +1 -4
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
- package/src/session-runtime/turn/turn-run.mjs +1 -36
- package/src/session-runtime/usage-stats-api.mjs +4 -1
- 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 +28 -36
- package/src/session-runtime/workspace/transition-steps.mjs +96 -95
- package/src/session-runtime/workspace/transitions.mjs +248 -68
- package/src/standalone/channel-transport.mjs +3 -4
- package/src/standalone/daemon-desktop-runtime.mjs +32 -1
- package/src/standalone/daemon-pg-stop.mjs +1 -3
- package/src/standalone/daemon-session-control.mjs +88 -0
- package/src/standalone/daemon-stored-session-views.mjs +23 -3
- package/src/standalone/daemon.mjs +2 -0
- package/src/standalone/local-session-runtime.mjs +11 -12
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-runtime-inline-host.mjs +2 -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-turns.mjs +17 -6
- package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
- package/src/standalone/session-tool-output.mjs +82 -0
- package/src/tui/app/app-view.jsx +2 -1
- 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/slash-commands.mjs +6 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
- package/src/tui/components/Spinner.jsx +4 -0
- package/src/tui/components/SpinnerByline.jsx +21 -2
- package/src/tui/components/status-line/local-line.mjs +10 -2
- package/src/tui/dist/index.mjs +478 -251
- package/src/tui/session/active-tool-summary.mjs +1 -1
- package/src/tui/session/context-state.mjs +0 -3
- 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 +4 -10
- package/src/tui/session/session-api/controls.mjs +12 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
- package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
- package/src/tui/session/session-api/lifecycle.mjs +13 -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 +3 -3
- package/src/tui/session/session-api/worktree.mjs +3 -2
- 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 +17 -8
- 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 +5 -1
- package/src/tui/session/turn.mjs +38 -31
- 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/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 -302
- 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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function stableItemKey(item, index) {
|
|
2
|
+
if (item?.id !== undefined && item?.id !== null) return String(item.id);
|
|
3
|
+
return `${item?.kind || 'item'}:${item?.text || item?.label || item?.status || ''}:${index}`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Completed attempts keep the same identity in live, full and tail projections. */
|
|
7
|
+
export function transcriptTurnKeys(items) {
|
|
8
|
+
const transcript = Array.isArray(items) ? items : [];
|
|
9
|
+
const keys = [];
|
|
10
|
+
let activeTurn = null;
|
|
11
|
+
let start = 0;
|
|
12
|
+
for (let index = 0; index < transcript.length; index += 1) {
|
|
13
|
+
const item = transcript[index];
|
|
14
|
+
if (item?.kind === 'user') {
|
|
15
|
+
activeTurn = `turn:${stableItemKey(item, index)}`;
|
|
16
|
+
start = index;
|
|
17
|
+
}
|
|
18
|
+
if (!activeTurn) {
|
|
19
|
+
activeTurn = `segment:${stableItemKey(item, index)}`;
|
|
20
|
+
start = index;
|
|
21
|
+
}
|
|
22
|
+
keys.push(activeTurn);
|
|
23
|
+
if (item?.kind === 'turndone') {
|
|
24
|
+
if (typeof item.attemptId === 'string' && item.attemptId) {
|
|
25
|
+
for (let row = start; row <= index; row += 1) keys[row] = `attempt:${item.attemptId}`;
|
|
26
|
+
} else if (typeof item.checkpointId === 'string' && item.checkpointId) {
|
|
27
|
+
for (let row = start; row <= index; row += 1) keys[row] = `checkpoint:${item.checkpointId}`;
|
|
28
|
+
}
|
|
29
|
+
activeTurn = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return keys;
|
|
33
|
+
}
|
|
@@ -29,7 +29,15 @@ export function conversationText(content) {
|
|
|
29
29
|
.join('\n');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function createTranscriptFile({
|
|
32
|
+
export function createTranscriptFile({
|
|
33
|
+
projectDir,
|
|
34
|
+
transcriptPath,
|
|
35
|
+
sessionId,
|
|
36
|
+
resolvedCwd,
|
|
37
|
+
logOnce,
|
|
38
|
+
rotation,
|
|
39
|
+
memoryEligible = true,
|
|
40
|
+
}) {
|
|
33
41
|
let projectDirReady = false;
|
|
34
42
|
function ensureProjectDir() {
|
|
35
43
|
if (projectDirReady) return;
|
|
@@ -81,6 +89,7 @@ export function createTranscriptFile({ projectDir, transcriptPath, sessionId, re
|
|
|
81
89
|
const text = conversationText(message?.content);
|
|
82
90
|
if (!text.trim()) continue;
|
|
83
91
|
const entry = { type: role, sessionId, cwd: resolvedCwd };
|
|
92
|
+
if (!memoryEligible) entry.memoryEligible = false;
|
|
84
93
|
if (message?.timestamp != null) entry.timestamp = message.timestamp;
|
|
85
94
|
else if (message?.ts != null) entry.ts = message.ts;
|
|
86
95
|
entry.message = { content: [{ type: 'text', text }] };
|
|
@@ -30,7 +30,7 @@ const textOrEmpty = (text) => {
|
|
|
30
30
|
return text == null ? '' : String(text);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {}) {
|
|
33
|
+
export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid, memoryEligible = true } = {}) {
|
|
34
34
|
if (!mixdogHome) throw new Error('transcript-writer: mixdogHome is required');
|
|
35
35
|
if (!sessionId) throw new Error('transcript-writer: sessionId is required');
|
|
36
36
|
if (!cwd) throw new Error('transcript-writer: cwd is required');
|
|
@@ -42,7 +42,15 @@ export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {})
|
|
|
42
42
|
|
|
43
43
|
const logOnce = createFailureLog();
|
|
44
44
|
const rotation = createRotationTracker({ transcriptPath, logOnce });
|
|
45
|
-
const file = createTranscriptFile({
|
|
45
|
+
const file = createTranscriptFile({
|
|
46
|
+
projectDir,
|
|
47
|
+
transcriptPath,
|
|
48
|
+
sessionId,
|
|
49
|
+
resolvedCwd,
|
|
50
|
+
logOnce,
|
|
51
|
+
rotation,
|
|
52
|
+
memoryEligible,
|
|
53
|
+
});
|
|
46
54
|
const record = createSessionRecord({ sessionRecordPath, sessionId, resolvedCwd, transcriptPath, logOnce });
|
|
47
55
|
|
|
48
56
|
noteSessionTranscript(sessionId, transcriptPath);
|
|
@@ -59,6 +67,7 @@ export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {})
|
|
|
59
67
|
entry?.timestamp != null || entry?.ts != null
|
|
60
68
|
? { ...entry, cwd: resolvedCwd }
|
|
61
69
|
: { ...entry, cwd: resolvedCwd, timestamp: formatUtcTimestamp() };
|
|
70
|
+
if (!memoryEligible) stampedEntry.memoryEligible = false;
|
|
62
71
|
const line = `${JSON.stringify(stampedEntry)}\n`;
|
|
63
72
|
appendBuffered(transcriptPath, line);
|
|
64
73
|
rotation.noteAppended(Buffer.byteLength(line));
|
|
@@ -95,7 +104,8 @@ export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {})
|
|
|
95
104
|
return {
|
|
96
105
|
transcriptPath,
|
|
97
106
|
sessionRecordPath,
|
|
98
|
-
|
|
107
|
+
// Agent runtimes can share a PID with their parent; never replace its discovery record.
|
|
108
|
+
writeSessionRecord: memoryEligible ? record.write : () => {},
|
|
99
109
|
ensureConversationBackfill: (messages) =>
|
|
100
110
|
isSessionTranscriptPurged(sessionId) ? false : file.ensureConversationBackfill(messages),
|
|
101
111
|
ensureTranscriptFile: () => {
|
|
@@ -103,7 +113,7 @@ export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {})
|
|
|
103
113
|
},
|
|
104
114
|
// Refresh only the session record's updatedAt so discovery keeps ranking
|
|
105
115
|
// this session as live across long-lived remote sessions.
|
|
106
|
-
refresh: record.write,
|
|
116
|
+
refresh: memoryEligible ? record.write : () => {},
|
|
107
117
|
appendAssistant: (text) => appendText('assistant', text),
|
|
108
118
|
// User prompt row. It exists so the memory transcript watcher ingests BOTH
|
|
109
119
|
// sides of the conversation (user rows were previously never written,
|
|
@@ -960,8 +960,9 @@ export async function sessionChangeReview(base, { baselines = null, records = []
|
|
|
960
960
|
(Boolean(entry.root) &&
|
|
961
961
|
insideDirectory(entry.root, entry.path) &&
|
|
962
962
|
changePathKey(relative(entry.root, entry.path)) === targetKey);
|
|
963
|
+
const baselineTarget = targetKey ? targeted : null;
|
|
963
964
|
const baselineUnits = baselines
|
|
964
|
-
? await baselineChanges(baselines, covered, unvouched, diffBudget, { target:
|
|
965
|
+
? await baselineChanges(baselines, covered, unvouched, diffBudget, { target: baselineTarget })
|
|
965
966
|
: [];
|
|
966
967
|
// Units named but not measured still claim their names (see `show`).
|
|
967
968
|
const measured = baselineUnits.filter((unit) => !unit.nameOnly);
|
|
@@ -649,7 +649,8 @@ function foldTrackedChanges(tracker, list, snapshot) {
|
|
|
649
649
|
const after = contentBuffer(raw?.after);
|
|
650
650
|
if (!destinationPath && before === null && after === null) continue;
|
|
651
651
|
dropUndoneChains(tracker, new Set([sourceKey, ...(destination ? [pathKey(destinationPath)] : [])]));
|
|
652
|
-
|
|
652
|
+
_writeSeq += 1;
|
|
653
|
+
const seq = _writeSeq;
|
|
653
654
|
appendSegment(tracker, {
|
|
654
655
|
before: { path: sourcePath, displayPath: sourceDisplay, content: before },
|
|
655
656
|
after: destination
|
|
@@ -1279,7 +1280,11 @@ export async function getTurnReviewFilePatch(_worktree, sessionId, file) {
|
|
|
1279
1280
|
// Only a file the recorded review lists, by its own path.
|
|
1280
1281
|
const record = await latestTurnRecord(ownerSessionId);
|
|
1281
1282
|
const match = record ? findChangeFile(record, path) : null;
|
|
1282
|
-
if (
|
|
1283
|
+
if (
|
|
1284
|
+
!match ||
|
|
1285
|
+
fileState(match) !== 'applied' ||
|
|
1286
|
+
changePathKey(match.path) !== changePathKey(relativeRecordPath(record, path))
|
|
1287
|
+
) {
|
|
1283
1288
|
return filePatchReply(path, '');
|
|
1284
1289
|
}
|
|
1285
1290
|
const patch = changeDisplayPatch(match);
|
|
@@ -1547,7 +1552,10 @@ async function latestRecordedCheckpoint(ownerSessionId) {
|
|
|
1547
1552
|
}
|
|
1548
1553
|
|
|
1549
1554
|
const recordFilePaths = (root, files) =>
|
|
1550
|
-
files
|
|
1555
|
+
files
|
|
1556
|
+
.flatMap((entry) => [entry.path, entry.oldPath])
|
|
1557
|
+
.filter(Boolean)
|
|
1558
|
+
.map((path) => resolve(root, path));
|
|
1551
1559
|
|
|
1552
1560
|
/** Undo a sealed turn's applied record files (all, or the one named). */
|
|
1553
1561
|
async function revertRecordedTurn(ownerSessionId, record, file) {
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { isAbsolute, join } from 'node:path';
|
|
5
|
+
import { withFileLockSync, writeJsonAtomicSync } from '../atomic-file.mjs';
|
|
6
|
+
import { isPidAliveOrUnknown } from '../pid-liveness.mjs';
|
|
7
|
+
import { findGitLayout } from './git-head.mjs';
|
|
8
|
+
import { canonicalPath, isWithin, samePath } from './paths.mjs';
|
|
9
|
+
import { worktreeError, worktreeRefusal } from './contract.mjs';
|
|
10
|
+
import { registryPath } from './registry.mjs';
|
|
11
|
+
|
|
12
|
+
const FILE = 'mixdog-workspace-admission.json';
|
|
13
|
+
const overlaps = (a, b) => isWithin(a, b) || isWithin(b, a);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The admission policy's external exception: a linked checkout Mixdog did not
|
|
17
|
+
* create (not a project's main folder, not a registered managed worktree) is
|
|
18
|
+
* never lock-blocked. Management takes no lease on it and its readers are
|
|
19
|
+
* admitted whatever is held elsewhere; its running readers still refuse a
|
|
20
|
+
* management action that touches it.
|
|
21
|
+
*/
|
|
22
|
+
export function isExternalCheckout(folder) {
|
|
23
|
+
const layout = findGitLayout(folder, { strict: true });
|
|
24
|
+
if (!layout || samePath(layout.gitDir, layout.commonDir)) return false;
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = JSON.parse(readFileSync(registryPath(), 'utf8'))?.entries ?? {};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error?.code !== 'ENOENT') throw worktreeRefusal('workspace-admission-unreadable');
|
|
30
|
+
entries = {};
|
|
31
|
+
}
|
|
32
|
+
return !Object.values(entries).some(
|
|
33
|
+
(record) => typeof record?.path === 'string' && samePath(record.path, layout.topLevel)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mutate(commonDir, update) {
|
|
38
|
+
const path = join(commonDir, FILE);
|
|
39
|
+
return withFileLockSync(
|
|
40
|
+
`${path}.lock`,
|
|
41
|
+
() => {
|
|
42
|
+
let state;
|
|
43
|
+
try {
|
|
44
|
+
state = JSON.parse(readFileSync(path, 'utf8'));
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error?.code === 'ENOENT') state = { version: 1, leases: [] };
|
|
47
|
+
else throw worktreeRefusal('workspace-admission-unreadable');
|
|
48
|
+
}
|
|
49
|
+
if (
|
|
50
|
+
state?.version !== 1 ||
|
|
51
|
+
!Array.isArray(state.leases) ||
|
|
52
|
+
state.leases.some(
|
|
53
|
+
(lease) =>
|
|
54
|
+
!lease ||
|
|
55
|
+
!['turn', 'command', 'management'].includes(lease.kind) ||
|
|
56
|
+
typeof lease.token !== 'string' ||
|
|
57
|
+
!Number.isSafeInteger(lease.pid) ||
|
|
58
|
+
lease.pid <= 0 ||
|
|
59
|
+
typeof lease.path !== 'string' ||
|
|
60
|
+
!isAbsolute(lease.path)
|
|
61
|
+
)
|
|
62
|
+
) {
|
|
63
|
+
throw worktreeRefusal('workspace-admission-unreadable');
|
|
64
|
+
}
|
|
65
|
+
// Only a pid PROVEN dead (ESRCH) drops its lease, whatever its kind; an
|
|
66
|
+
// unprobeable pid still counts as running. A live runtime keeps a command
|
|
67
|
+
// whose tree it cannot verify reserved under its own (live) pid until the
|
|
68
|
+
// tree is confirmed gone, so a lease that outlives every pid belongs to a
|
|
69
|
+
// restarted runtime that can no longer release it.
|
|
70
|
+
state.leases = state.leases.filter((lease) => isPidAliveOrUnknown(lease.pid));
|
|
71
|
+
const result = update(state.leases);
|
|
72
|
+
writeJsonAtomicSync(path, state, { lock: false });
|
|
73
|
+
return result;
|
|
74
|
+
},
|
|
75
|
+
{ strictOwner: true }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function release(commonDir, token) {
|
|
80
|
+
let done = false;
|
|
81
|
+
return () => {
|
|
82
|
+
if (done) return;
|
|
83
|
+
mutate(commonDir, (leases) => {
|
|
84
|
+
for (let i = leases.length - 1; i >= 0; i--) if (leases[i].token === token) leases.splice(i, 1);
|
|
85
|
+
});
|
|
86
|
+
done = true;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Readers share a checkout freely. Null means a management writer currently
|
|
92
|
+
* owns it; a reader in an external checkout is never held back.
|
|
93
|
+
*/
|
|
94
|
+
export function tryAdmitWorkspaceActivity(sessionId, cwd, { pid = process.pid, kind = 'turn' } = {}) {
|
|
95
|
+
if (!['turn', 'command'].includes(kind)) throw new TypeError('Invalid workspace reader kind.');
|
|
96
|
+
const layout = findGitLayout(cwd, { strict: true });
|
|
97
|
+
if (!layout) return () => {};
|
|
98
|
+
const external = isExternalCheckout(cwd);
|
|
99
|
+
const path = canonicalPath(cwd);
|
|
100
|
+
const token = randomUUID();
|
|
101
|
+
const admitted = mutate(layout.commonDir, (leases) => {
|
|
102
|
+
if (!external && leases.some((lease) => lease.kind === 'management' && overlaps(lease.path, path))) return false;
|
|
103
|
+
leases.push({ kind, sessionId, pid, token, path });
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
106
|
+
return admitted ? release(layout.commonDir, token) : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function admitWorkspaceActivity(sessionId, cwd, signal, options) {
|
|
110
|
+
for (;;) {
|
|
111
|
+
signal?.throwIfAborted();
|
|
112
|
+
const leave = tryAdmitWorkspaceActivity(sessionId, cwd, options);
|
|
113
|
+
if (leave) {
|
|
114
|
+
if (signal?.aborted) {
|
|
115
|
+
leave();
|
|
116
|
+
signal.throwIfAborted();
|
|
117
|
+
}
|
|
118
|
+
return leave;
|
|
119
|
+
}
|
|
120
|
+
await delay(100, undefined, { signal });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Writers refuse while any process's reader overlaps an affected folder, then
|
|
126
|
+
* exclude later readers until the returned release is called. External
|
|
127
|
+
* checkouts are only checked, never held. `sessionId`: the acting session,
|
|
128
|
+
* whose own readers in this process do not count (its idleness is the
|
|
129
|
+
* caller's check).
|
|
130
|
+
*/
|
|
131
|
+
export function holdWorkspaceActivity(folders, { sessionId = null } = {}) {
|
|
132
|
+
const groups = new Map();
|
|
133
|
+
for (const folder of folders) {
|
|
134
|
+
const layout = findGitLayout(folder, { strict: true });
|
|
135
|
+
if (!layout) throw worktreeError('WORKTREE_UNSUPPORTED_REPO');
|
|
136
|
+
const paths = groups.get(layout.commonDir) ?? [];
|
|
137
|
+
paths.push({ path: canonicalPath(folder), external: isExternalCheckout(folder) });
|
|
138
|
+
groups.set(layout.commonDir, paths);
|
|
139
|
+
}
|
|
140
|
+
const releases = [];
|
|
141
|
+
try {
|
|
142
|
+
for (const [commonDir, paths] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
|
|
143
|
+
const token = randomUUID();
|
|
144
|
+
mutate(commonDir, (leases) => {
|
|
145
|
+
const conflicts = leases.filter(
|
|
146
|
+
(lease) =>
|
|
147
|
+
!(sessionId && lease.sessionId === sessionId && lease.pid === process.pid) &&
|
|
148
|
+
paths.some(({ path }) => overlaps(path, lease.path))
|
|
149
|
+
);
|
|
150
|
+
if (conflicts.length) throw worktreeRefusal('folder-in-use');
|
|
151
|
+
for (const { path, external } of paths) {
|
|
152
|
+
if (!external) leases.push({ kind: 'management', pid: process.pid, token, path });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
releases.push(release(commonDir, token));
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
releaseAll(releases);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
return () => releaseAll(releases);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Runs every release in reverse order even when one throws; the first failure is rethrown after all ran. */
|
|
165
|
+
export function releaseAll(releases) {
|
|
166
|
+
let failure = null;
|
|
167
|
+
for (const done of [...releases].reverse()) {
|
|
168
|
+
try {
|
|
169
|
+
done();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
failure ??= error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (failure) throw failure;
|
|
175
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { branchCommit, inspectRepo, listWorktrees, runGit, validBranchName } from './git.mjs';
|
|
3
|
+
import { worktreeError, worktreeRefusal } from './contract.mjs';
|
|
4
|
+
import { samePath } from './paths.mjs';
|
|
5
|
+
import { getRecord, putRecord, readRegistry } from './registry.mjs';
|
|
6
|
+
import { readOwnerLease } from './owner.mjs';
|
|
7
|
+
import { isPidAliveOrUnknown } from '../pid-liveness.mjs';
|
|
8
|
+
import { operationInProgress, uncommittedPaths } from './merge.mjs';
|
|
9
|
+
import { gitStatusRunner } from './git.mjs';
|
|
10
|
+
import {
|
|
11
|
+
withRepoLock,
|
|
12
|
+
getWorktreeByPath,
|
|
13
|
+
mergeBranchIntoProject,
|
|
14
|
+
assertWorktreeRemovable,
|
|
15
|
+
removeManagedWorktree,
|
|
16
|
+
managedWorktreeDirtyPaths,
|
|
17
|
+
} from './manager.mjs';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* WORKTREE_UNSUPPORTED_REPO when the project folder has no checked-out branch
|
|
21
|
+
* to merge into; WORKTREE_TARGET_MISMATCH when the confirmed `target` (null:
|
|
22
|
+
* none confirmed) is no longer the checked-out branch.
|
|
23
|
+
*/
|
|
24
|
+
function assertMergeTarget(currentBranch, target) {
|
|
25
|
+
if (!currentBranch) {
|
|
26
|
+
throw worktreeError('WORKTREE_UNSUPPORTED_REPO', 'The project folder has no checked-out branch to merge into.');
|
|
27
|
+
}
|
|
28
|
+
if (target !== null && target !== currentBranch) {
|
|
29
|
+
throw worktreeError(
|
|
30
|
+
'WORKTREE_TARGET_MISMATCH',
|
|
31
|
+
`The project folder is now on "${currentBranch}"; confirm the merge again.`,
|
|
32
|
+
{ target, current: currentBranch }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function inspect(projectPath, branch) {
|
|
38
|
+
const repo = await inspectRepo(projectPath);
|
|
39
|
+
if (!(await validBranchName(repo.projectPath, branch))) throw new TypeError('Invalid local branch name.');
|
|
40
|
+
const commit = await branchCommit(repo.projectPath, branch);
|
|
41
|
+
if (!commit) throw worktreeError('WORKTREE_MISSING', 'The local branch no longer exists.');
|
|
42
|
+
const holder = (await listWorktrees(repo.projectPath)).find((row) => row.branch === branch && !row.prunable);
|
|
43
|
+
const entry = holder ? await getWorktreeByPath(holder.path) : null;
|
|
44
|
+
return {
|
|
45
|
+
repo,
|
|
46
|
+
branch,
|
|
47
|
+
commit,
|
|
48
|
+
target: repo.currentBranch,
|
|
49
|
+
targetCommit: repo.currentBranch ? await branchCommit(repo.projectPath, repo.currentBranch) : null,
|
|
50
|
+
path: holder?.path ?? null,
|
|
51
|
+
worktreeId: entry?.id ?? null,
|
|
52
|
+
kind: holder ? (samePath(holder.path, repo.topLevel) ? 'project' : entry ? 'managed' : 'external') : 'free',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function inspectProjectBranch({ projectPath, branch }) {
|
|
57
|
+
const repo = await inspectRepo(projectPath);
|
|
58
|
+
return withRepoLock(repo.repoCommonDir, async () => {
|
|
59
|
+
const { repo: _repo, ...state } = await inspect(projectPath, branch);
|
|
60
|
+
return state;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A merge confirmed by its target branch only (the dock, a session merging
|
|
66
|
+
* its own checkout): the identities inspected now are the confirmed ones, and
|
|
67
|
+
* the merge then takes the one guarded path (manageProjectBranch). `target`
|
|
68
|
+
* null accepts the folder's checked-out branch.
|
|
69
|
+
*/
|
|
70
|
+
export async function mergeConfirmedTarget({ projectPath, branch, target = null }, withFolders) {
|
|
71
|
+
const state = await inspectProjectBranch({ projectPath, branch });
|
|
72
|
+
assertMergeTarget(state.target, target);
|
|
73
|
+
return manageProjectBranch(
|
|
74
|
+
{
|
|
75
|
+
projectPath,
|
|
76
|
+
branch,
|
|
77
|
+
action: 'merge',
|
|
78
|
+
commit: state.commit,
|
|
79
|
+
target: state.target,
|
|
80
|
+
targetCommit: state.targetCommit,
|
|
81
|
+
confirmed: true,
|
|
82
|
+
},
|
|
83
|
+
withFolders
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The one guarded path of every merge, folder cleanup and branch deletion.
|
|
89
|
+
* The caller supplies process/session admission (`withFolders(folders,
|
|
90
|
+
* removing, run)`: a merge holds both the project folder and the source
|
|
91
|
+
* checkout) while this module owns Git/ref validation: a merge refuses a dirty
|
|
92
|
+
* source or one with a Git operation in progress, and the destination's own
|
|
93
|
+
* preflight refuses a dirty target.
|
|
94
|
+
*/
|
|
95
|
+
export async function manageProjectBranch(input, withFolders) {
|
|
96
|
+
const { projectPath, branch, action, commit, target, targetCommit } = input;
|
|
97
|
+
if (input.confirmed !== true) throw worktreeError('WORKTREE_CONFIRM_REQUIRED');
|
|
98
|
+
if (!['merge', 'remove-worktree', 'delete-branch'].includes(action)) throw new TypeError('Unknown branch action.');
|
|
99
|
+
const initial = await inspect(projectPath, branch);
|
|
100
|
+
const folders =
|
|
101
|
+
action === 'merge'
|
|
102
|
+
? [initial.repo.topLevel, initial.path].filter(Boolean)
|
|
103
|
+
: action === 'remove-worktree' && initial.path
|
|
104
|
+
? [initial.path]
|
|
105
|
+
: [];
|
|
106
|
+
return withFolders([...new Set(folders)], action === 'remove-worktree', async (retainedSessionIds) =>
|
|
107
|
+
withRepoLock(initial.repo.repoCommonDir, async () => {
|
|
108
|
+
const state = await inspect(projectPath, branch);
|
|
109
|
+
if (
|
|
110
|
+
state.commit !== commit ||
|
|
111
|
+
state.path !== initial.path ||
|
|
112
|
+
(action !== 'remove-worktree' && (state.target !== target || state.targetCommit !== targetCommit))
|
|
113
|
+
) {
|
|
114
|
+
throw worktreeError('WORKTREE_REVISION_CONFLICT', 'The branch changed; reopen the menu and confirm again.');
|
|
115
|
+
}
|
|
116
|
+
const record = state.worktreeId ? await getRecord(state.worktreeId) : null;
|
|
117
|
+
if (record) {
|
|
118
|
+
const leases = [
|
|
119
|
+
await readOwnerLease(record, { strict: true }),
|
|
120
|
+
...Object.values(record.sharedLeases ?? {}),
|
|
121
|
+
].filter(Boolean);
|
|
122
|
+
if (
|
|
123
|
+
leases.some(
|
|
124
|
+
(lease) => isPidAliveOrUnknown(lease.pid) && (action === 'remove-worktree' || lease.pid !== process.pid)
|
|
125
|
+
)
|
|
126
|
+
) {
|
|
127
|
+
throw worktreeRefusal('workspace-lease');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (action === 'remove-worktree') {
|
|
131
|
+
if (
|
|
132
|
+
state.kind !== 'managed' ||
|
|
133
|
+
!record ||
|
|
134
|
+
!(record.state === 'ready' || (record.state === 'deleting' && record.deleting?.safeOnly)) ||
|
|
135
|
+
!existsSync(record.path)
|
|
136
|
+
) {
|
|
137
|
+
throw worktreeError('WORKTREE_PATH_UNSAFE', 'Only an unused Mixdog-managed worktree can be removed.');
|
|
138
|
+
}
|
|
139
|
+
const retained = new Set(retainedSessionIds);
|
|
140
|
+
const users = [record.ownerSessionId, ...(record.sharedSessionIds ?? [])].filter((id) => retained.has(id));
|
|
141
|
+
if (users.length) throw worktreeRefusal('workspace-retained', { sessions: users });
|
|
142
|
+
await assertWorktreeRemovable({ entryId: record.id });
|
|
143
|
+
return removeManagedWorktree({ entryId: record.id, preserveBranch: true, safeOnly: true });
|
|
144
|
+
}
|
|
145
|
+
if (!target || branch === target)
|
|
146
|
+
throw worktreeError('WORKTREE_TARGET_MISMATCH', 'Select a different source branch.');
|
|
147
|
+
if (action === 'delete-branch') {
|
|
148
|
+
if (state.path)
|
|
149
|
+
throw worktreeError('WORKTREE_BRANCH_OCCUPIED', 'Remove or leave the checkout before deleting its branch.');
|
|
150
|
+
const held = Object.values((await readRegistry()).entries).some(
|
|
151
|
+
(row) => row.branch === branch && samePath(row.repoCommonDir, state.repo.repoCommonDir)
|
|
152
|
+
);
|
|
153
|
+
if (held) throw worktreeError('WORKTREE_BUSY_JOBS', 'A managed worktree still retains this branch.');
|
|
154
|
+
const merged = await runGit(projectPath, ['merge-base', '--is-ancestor', commit, targetCommit], {
|
|
155
|
+
allowFailure: true,
|
|
156
|
+
});
|
|
157
|
+
if (merged.code !== 0) throw worktreeError('WORKTREE_UNMERGED', `The branch is not merged into ${target}.`);
|
|
158
|
+
await runGit(projectPath, ['branch', '-d', '--', branch]);
|
|
159
|
+
return { deleted: true, branch };
|
|
160
|
+
}
|
|
161
|
+
if (state.path) {
|
|
162
|
+
const operation = await operationInProgress(gitStatusRunner, state.path);
|
|
163
|
+
const dirty = record
|
|
164
|
+
? await managedWorktreeDirtyPaths(record.id)
|
|
165
|
+
: await uncommittedPaths(gitStatusRunner, state.path);
|
|
166
|
+
if (operation || dirty.length)
|
|
167
|
+
throw worktreeError('WORKTREE_DIRTY', 'Commit or finish the source work before merging.', {
|
|
168
|
+
operation,
|
|
169
|
+
count: dirty.length,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const merged = await mergeBranchIntoProject({ projectPath, source: branch, target, confirmed: true });
|
|
173
|
+
// A managed worktree remembers where it was merged (its later removal checks against it).
|
|
174
|
+
const current = merged.merged && record ? await getRecord(record.id) : null;
|
|
175
|
+
if (current && current.mergeTarget !== target) await putRecord({ ...current, mergeTarget: target });
|
|
176
|
+
return merged;
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -48,8 +48,8 @@ import { operationInProgress } from './merge.mjs';
|
|
|
48
48
|
const IDENTITY_PATH = '.mixdog/project.id';
|
|
49
49
|
const RECOVERY_REF_PREFIX = 'refs/mixdog/carry/';
|
|
50
50
|
/** Untracked files beyond these limits are refused rather than snapshotted. */
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const CARRY_UNTRACKED_MAX_BYTES = 256 * 1024 * 1024;
|
|
52
|
+
const CARRY_UNTRACKED_MAX_FILES = 10_000;
|
|
53
53
|
const CHECK_ATTR_CHUNK = 200;
|
|
54
54
|
// Recovery commits are Mixdog's own, independent of the user's identity config.
|
|
55
55
|
const COMMIT_ENV = Object.freeze({
|
|
@@ -66,6 +66,8 @@ function identityPath(projectSubdir) {
|
|
|
66
66
|
/** CE_INTENT_TO_ADD in an index entry's flags (`git add -N`). */
|
|
67
67
|
const INTENT_TO_ADD_FLAG = 0x20000000;
|
|
68
68
|
|
|
69
|
+
const PORCELAIN_LEAD_FIELDS = { 1: 8, 2: 9, u: 10 };
|
|
70
|
+
|
|
69
71
|
/**
|
|
70
72
|
* `git status --porcelain=v2 -z` rows: path, untracked, unmerged, submodule.
|
|
71
73
|
* @returns {Array<{ path: string, untracked: boolean, unmerged: boolean, submodule: boolean }>}
|
|
@@ -82,8 +84,8 @@ function parseStatusV2(raw) {
|
|
|
82
84
|
continue;
|
|
83
85
|
}
|
|
84
86
|
// Leading fields before the path: 1 → 8, 2 → 9 (then the original path), u → 10.
|
|
85
|
-
const lead = kind
|
|
86
|
-
if (lead
|
|
87
|
+
const lead = PORCELAIN_LEAD_FIELDS[kind];
|
|
88
|
+
if (lead === undefined) continue;
|
|
87
89
|
const parts = field.split(' ');
|
|
88
90
|
rows.push({
|
|
89
91
|
path: parts.slice(lead).join(' '),
|
|
@@ -59,7 +59,8 @@ const DEFAULT_ERROR_MESSAGES = Object.freeze({
|
|
|
59
59
|
WORKTREE_DIRTY: 'The worktree has uncommitted changes.',
|
|
60
60
|
WORKTREE_DIRTY_CHOICE:
|
|
61
61
|
'The project folder has uncommitted changes; choose whether to leave them there or carry them into the worktree.',
|
|
62
|
-
WORKTREE_FILE_RECOVERY:
|
|
62
|
+
WORKTREE_FILE_RECOVERY:
|
|
63
|
+
'Retained file recovery records block worktree removal, including force. Inspect and explicitly delete them first.',
|
|
63
64
|
WORKTREE_UNMERGED: 'The worktree branch has commits that are not merged.',
|
|
64
65
|
WORKTREE_MISSING: 'The managed worktree no longer exists.',
|
|
65
66
|
WORKTREE_UNSUPPORTED_REPO: 'This repository does not support managed worktrees.',
|
|
@@ -105,6 +106,31 @@ export function worktreeError(code, message, details) {
|
|
|
105
106
|
return new WorktreeError(code, message, details);
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Why a workspace management action (merge, folder cleanup, branch deletion,
|
|
111
|
+
* checkout) was refused because something still uses an affected folder. Every
|
|
112
|
+
* surface shows the same meaning: the runtime throws exactly this text (TUI,
|
|
113
|
+
* remote) and the desktop translates the same English source string.
|
|
114
|
+
* @readonly
|
|
115
|
+
*/
|
|
116
|
+
export const WORKTREE_REFUSAL_REASONS = Object.freeze({
|
|
117
|
+
'workspace-retained': 'Sessions still retain this folder. Close or remove those sessions before cleaning it up.',
|
|
118
|
+
'workspace-lease': 'A runtime still holds this folder. Stop its work and release the workspace first.',
|
|
119
|
+
'folder-in-use': 'Sessions or workers still use an affected folder. Finish or move them before continuing.',
|
|
120
|
+
'folder-shell-jobs': 'Background commands still use an affected folder. Wait for them or stop them first.',
|
|
121
|
+
'workspace-admission-unreadable': 'Workspace activity cannot be verified. Nothing was changed.',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* WORKTREE_BUSY_JOBS with the shared text of `reason` (WORKTREE_REFUSAL_REASONS).
|
|
126
|
+
* @param {keyof typeof WORKTREE_REFUSAL_REASONS} reason
|
|
127
|
+
* @param {Record<string, unknown>} [details]
|
|
128
|
+
*/
|
|
129
|
+
export function worktreeRefusal(reason, details = {}) {
|
|
130
|
+
if (!Object.hasOwn(WORKTREE_REFUSAL_REASONS, reason)) throw new TypeError(`Unknown refusal reason "${reason}".`);
|
|
131
|
+
return worktreeError('WORKTREE_BUSY_JOBS', WORKTREE_REFUSAL_REASONS[reason], { ...details, reason });
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
/**
|
|
109
135
|
* @param {unknown} error
|
|
110
136
|
* @param {WorktreeErrorCode} [code] When given, also require this code.
|