mixdog 1.3.1 → 1.3.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 +8 -4
- package/package.json +8 -8
- package/scripts/native-release-status.mjs +238 -0
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +9 -1
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +7 -10
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +109 -33
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +66 -16
- package/src/runtime/effort-judge/model-install.mjs +47 -7
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +135 -29
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +35 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +10 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +277 -124
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +92 -61
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +29 -27
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +542 -204
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +81 -35
- package/src/session-runtime/agent-task-sessions.mjs +811 -179
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +48 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +16 -7
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +23 -3
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +31 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +26 -6
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +12 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +48 -369
- package/src/session-runtime/tool-surface.mjs +10 -4
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +7 -2
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +173 -15
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +33 -10
- package/src/standalone/local-session-runtime.mjs +33 -16
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +36 -9
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +617 -369
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +52 -18
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +142 -14
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
- package/src/session-runtime/room-member-gate.mjs +0 -48
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
* reachable at `refs/mixdog/removed/<id>` (returned as `recoveryRef`/`tip`;
|
|
27
27
|
* `git branch <name> refs/mixdog/removed/<id>` restores it, `git update-ref
|
|
28
28
|
* -d` forgets it).
|
|
29
|
+
*
|
|
30
|
+
* A snapshot (createSnapshotWorktree) is a read-only detached checkout in the
|
|
31
|
+
* same store and registry, bound to no session and removed with the turn
|
|
32
|
+
* that made it (or by the prune / startup reconcile once its holder is gone).
|
|
29
33
|
*/
|
|
30
34
|
import { existsSync } from 'node:fs';
|
|
31
35
|
import {
|
|
@@ -42,6 +46,7 @@ import {
|
|
|
42
46
|
normalizeWorktreeOwner,
|
|
43
47
|
runRootCleanups,
|
|
44
48
|
runRootReleasers,
|
|
49
|
+
WORKTREE_SNAPSHOT_KIND,
|
|
45
50
|
worktreeBranchName,
|
|
46
51
|
worktreeError,
|
|
47
52
|
} from './contract.mjs';
|
|
@@ -79,6 +84,8 @@ import {
|
|
|
79
84
|
} from './include.mjs';
|
|
80
85
|
import { linkWorktreeDependencies } from './dependencies.mjs';
|
|
81
86
|
import { withRepoLock as withRepoMutationLock } from './lock.mjs';
|
|
87
|
+
import { holdWorkspaceActivity } from './activity-gate.mjs';
|
|
88
|
+
import { withAdvisoryLocks } from '../../agent/orchestrator/tools/builtin/advisory-lock.mjs';
|
|
82
89
|
import { mergeBranchIntoCheckout, operationInProgress } from './merge.mjs';
|
|
83
90
|
import { readOwnerLease, removeOwnerLease, sameOwner, writeOwnerLease } from './owner.mjs';
|
|
84
91
|
import { canonicalPath, checkoutFolder, isWithin, pathKey, projectSubdirOf, samePath } from './paths.mjs';
|
|
@@ -318,6 +325,8 @@ async function recordInclude(record, ledger) {
|
|
|
318
325
|
}
|
|
319
326
|
|
|
320
327
|
async function unmergedCommitCount(record) {
|
|
328
|
+
// A snapshot has no branch, so nothing of it can be unmerged.
|
|
329
|
+
if (!record.branch) return 0;
|
|
321
330
|
const cwd = repoCwd(record);
|
|
322
331
|
const tip = await branchCommit(cwd, record.branch);
|
|
323
332
|
if (!tip) return 0;
|
|
@@ -591,6 +600,20 @@ async function rollbackCreation(record, cause) {
|
|
|
591
600
|
return { complete: false, remaining };
|
|
592
601
|
}
|
|
593
602
|
|
|
603
|
+
/** finishDeletion's folder step; what is left over goes to `remaining`. */
|
|
604
|
+
async function removeFolder(record, remaining) {
|
|
605
|
+
await removeOwnerLease(record);
|
|
606
|
+
const ctx = { reason: 'worktree-removed', entryId: record.id };
|
|
607
|
+
const results = await runRootReleasers(record.path, ctx);
|
|
608
|
+
for (const root of new Set([record.path, sessionRootOf(record)])) {
|
|
609
|
+
results.push(...(await runRootCleanups(root, ctx)));
|
|
610
|
+
}
|
|
611
|
+
for (const result of results) {
|
|
612
|
+
if (!result.ok) remaining.push({ kind: 'cleanup', name: result.name, error: result.error.message });
|
|
613
|
+
}
|
|
614
|
+
remaining.push(...(await (record.deleting?.safeOnly ? removeCheckoutSafely(record) : removeCheckout(record))));
|
|
615
|
+
}
|
|
616
|
+
|
|
594
617
|
/**
|
|
595
618
|
* Finish (or resume) a decided deletion, in order: release the live handles
|
|
596
619
|
* on the folder (desktop terminals), drop the caches keyed by it (the checkout
|
|
@@ -602,16 +625,14 @@ async function finishDeletion(record) {
|
|
|
602
625
|
await assertRecoveryStoresClear(record);
|
|
603
626
|
const remaining = [];
|
|
604
627
|
try {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
628
|
+
// A snapshot is deleted under the cross-process write lock the git tool
|
|
629
|
+
// takes on the worktree it writes in (withGitWriteLocks: its advisory
|
|
630
|
+
// lock), so no git write of another process runs while the folder goes.
|
|
631
|
+
if (record.kind === WORKTREE_SNAPSHOT_KIND) {
|
|
632
|
+
await withAdvisoryLocks([record.path], () => removeFolder(record, remaining));
|
|
633
|
+
} else {
|
|
634
|
+
await removeFolder(record, remaining);
|
|
610
635
|
}
|
|
611
|
-
for (const result of results) {
|
|
612
|
-
if (!result.ok) remaining.push({ kind: 'cleanup', name: result.name, error: result.error.message });
|
|
613
|
-
}
|
|
614
|
-
remaining.push(...(await (record.deleting?.safeOnly ? removeCheckoutSafely(record) : removeCheckout(record))));
|
|
615
636
|
} catch (error) {
|
|
616
637
|
remaining.push({ kind: 'error', error: String(error?.message || error) });
|
|
617
638
|
}
|
|
@@ -623,7 +644,7 @@ async function finishDeletion(record) {
|
|
|
623
644
|
let branch = { deleted: false, reason: 'kept' };
|
|
624
645
|
let kept = { tip: null, recoveryRef: null };
|
|
625
646
|
const merged = (await unmergedCommitCount(record)) === 0;
|
|
626
|
-
if (!record.deleting?.preserveBranch && (record.deleting?.force || merged)) {
|
|
647
|
+
if (record.branch && !record.deleting?.preserveBranch && (record.deleting?.force || merged)) {
|
|
627
648
|
// Forced past unmerged commits: the tip stays reachable before `branch -D`.
|
|
628
649
|
if (!merged) kept = await keepRemovedTip(record);
|
|
629
650
|
else kept.tip = await branchCommit(repoCwd(record), record.branch);
|
|
@@ -672,11 +693,16 @@ export async function getWorktreeByPath(path) {
|
|
|
672
693
|
return record ? entryView(record) : null;
|
|
673
694
|
}
|
|
674
695
|
|
|
675
|
-
/**
|
|
696
|
+
/**
|
|
697
|
+
* Resolve a branch to its checkout without switching any existing folder.
|
|
698
|
+
* `startPoint` (with `create`): the new branch starts at that commit-ish
|
|
699
|
+
* instead of the project folder's branch.
|
|
700
|
+
*/
|
|
676
701
|
export async function resolveBranchWorkspace({
|
|
677
702
|
projectPath,
|
|
678
703
|
branch,
|
|
679
704
|
create = false,
|
|
705
|
+
startPoint = null,
|
|
680
706
|
sessionId,
|
|
681
707
|
runtimeToken,
|
|
682
708
|
txId,
|
|
@@ -684,6 +710,7 @@ export async function resolveBranchWorkspace({
|
|
|
684
710
|
signal,
|
|
685
711
|
} = {}) {
|
|
686
712
|
requireText(branch, 'branch');
|
|
713
|
+
if (startPoint !== null && create !== true) throw new TypeError('startPoint needs create.');
|
|
687
714
|
const repo = await inspectRepo(projectPath, { signal });
|
|
688
715
|
return withRepoLock(repo.repoCommonDir, async () => {
|
|
689
716
|
if (!(await validBranchName(repo.projectPath, branch))) throw new TypeError('Invalid branch name.');
|
|
@@ -738,6 +765,7 @@ export async function resolveBranchWorkspace({
|
|
|
738
765
|
branch,
|
|
739
766
|
existingBranch: !create,
|
|
740
767
|
baseBranch: create ? repo.currentBranch : branch,
|
|
768
|
+
startCommit: startPoint,
|
|
741
769
|
nameHint: branch,
|
|
742
770
|
confirmLarge,
|
|
743
771
|
signal,
|
|
@@ -901,6 +929,53 @@ async function diskFullFailure(error, { needed, root }) {
|
|
|
901
929
|
});
|
|
902
930
|
}
|
|
903
931
|
|
|
932
|
+
/**
|
|
933
|
+
* The size preflight of every managed checkout of `baseCommit`:
|
|
934
|
+
* WORKTREE_DISK_INSUFFICIENT when it cannot fit, WORKTREE_LARGE_REPO_CONFIRM
|
|
935
|
+
* (`details: { estimateBytes, thresholdBytes }`) above the warning size until
|
|
936
|
+
* `confirmLarge`. `extraBytes`: include copies on top of the tracked files.
|
|
937
|
+
*/
|
|
938
|
+
async function assertCheckoutFits({ repo, baseCommit, extraBytes = 0, settings, confirmLarge, signal }) {
|
|
939
|
+
const tracked = await trackedCheckoutBytes(repo.topLevel, baseCommit, { signal });
|
|
940
|
+
const contentBytes = tracked.bytes + extraBytes;
|
|
941
|
+
const root = managedWorktreeRoot(repo.repoCommonDir);
|
|
942
|
+
const needed = requiredBytes(contentBytes);
|
|
943
|
+
const free = await config.freeBytes(root);
|
|
944
|
+
if (free < needed) {
|
|
945
|
+
throw worktreeError('WORKTREE_DISK_INSUFFICIENT', undefined, {
|
|
946
|
+
requiredBytes: needed,
|
|
947
|
+
freeBytes: free,
|
|
948
|
+
path: root,
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
if (settings.sizeWarnBytes > 0 && contentBytes > settings.sizeWarnBytes && confirmLarge !== true) {
|
|
952
|
+
throw worktreeError('WORKTREE_LARGE_REPO_CONFIRM', undefined, {
|
|
953
|
+
estimateBytes: contentBytes,
|
|
954
|
+
thresholdBytes: settings.sizeWarnBytes,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
return { tracked, root, needed };
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/** The full id of commit-ish `value` in `cwd`; TypeError when it names no commit. */
|
|
961
|
+
async function resolveCommit(cwd, value, signal) {
|
|
962
|
+
const name = String(value ?? '').trim();
|
|
963
|
+
if (!name || name.startsWith('-')) throw new TypeError('A commit is required.');
|
|
964
|
+
const result = await runGit(cwd, ['rev-parse', '--verify', '--quiet', `${name}^{commit}`], {
|
|
965
|
+
signal,
|
|
966
|
+
allowFailure: true,
|
|
967
|
+
});
|
|
968
|
+
if (result.code !== 0) throw new TypeError(`Commit ${name} does not exist.`);
|
|
969
|
+
return firstLine(result.stdout);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/** A snapshot is in use while its holder session is live, or its lease holder runs in another process. */
|
|
973
|
+
async function snapshotHolderLive(record, live) {
|
|
974
|
+
if (record.holderSessionId && live.has(record.holderSessionId)) return true;
|
|
975
|
+
const lease = await readOwnerLease(record);
|
|
976
|
+
return !!lease && lease.pid !== process.pid && leaseProcessAlive(lease);
|
|
977
|
+
}
|
|
978
|
+
|
|
904
979
|
/**
|
|
905
980
|
* `carryChanges` decides the project folder's uncommitted changes (tracked
|
|
906
981
|
* and untracked, ignored excluded), judged under the repository lock:
|
|
@@ -913,9 +988,12 @@ async function diskFullFailure(error, { needed, root }) {
|
|
|
913
988
|
* untracked files. The result's `changes` (`{ mode: 'carried'|'left',
|
|
914
989
|
* count, recoveryRef? }`) reports what happened to them; a carried move's
|
|
915
990
|
* `recoveryRef` stays until dropCarryRecoveryRef / restoreCarriedChanges.
|
|
991
|
+
* `startCommit` (a new exact `branch` only) starts the branch at that
|
|
992
|
+
* commit-ish instead of the base branch's tip.
|
|
916
993
|
* @param {{ projectPath: string, sessionId: string, runtimeToken: string, baseBranch?: string|null,
|
|
917
994
|
* nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal,
|
|
918
|
-
* carryChanges?: 'ask'|'leave'|'carry'|null
|
|
995
|
+
* carryChanges?: 'ask'|'leave'|'carry'|null, branch?: string|null, existingBranch?: boolean,
|
|
996
|
+
* startCommit?: string|null }} input
|
|
919
997
|
*/
|
|
920
998
|
export async function createManagedWorktree({
|
|
921
999
|
projectPath,
|
|
@@ -929,9 +1007,13 @@ export async function createManagedWorktree({
|
|
|
929
1007
|
carryChanges = null,
|
|
930
1008
|
branch: requestedBranch = null,
|
|
931
1009
|
existingBranch = false,
|
|
1010
|
+
startCommit = null,
|
|
932
1011
|
} = {}) {
|
|
933
1012
|
requireText(projectPath, 'projectPath');
|
|
934
1013
|
requireText(txId, 'txId');
|
|
1014
|
+
if (startCommit !== null && (existingBranch || requestedBranch === null)) {
|
|
1015
|
+
throw new TypeError('startCommit applies to a new exact branch only.');
|
|
1016
|
+
}
|
|
935
1017
|
if (![null, 'ask', 'leave', 'carry'].includes(carryChanges)) {
|
|
936
1018
|
throw new TypeError('carryChanges must be ask, leave or carry.');
|
|
937
1019
|
}
|
|
@@ -970,30 +1052,22 @@ export async function createManagedWorktree({
|
|
|
970
1052
|
}
|
|
971
1053
|
mergeTarget = baseBranch;
|
|
972
1054
|
}
|
|
1055
|
+
// A new branch at an explicit start point (`git worktree add -b <b> <path> <commit-ish>`).
|
|
1056
|
+
if (startCommit) baseCommit = await resolveCommit(repo.projectPath, startCommit, signal);
|
|
973
1057
|
|
|
974
1058
|
// The main checkout's local env files come along on their own;
|
|
975
1059
|
// `.worktreeinclude` applies on top.
|
|
976
1060
|
const includePlan = await planIncludes(repo.topLevel, await readIncludeEntries(repo.topLevel), {
|
|
977
1061
|
envFiles: await untrackedEnvFiles(repo.topLevel),
|
|
978
1062
|
});
|
|
979
|
-
const tracked = await
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
freeBytes: free,
|
|
988
|
-
path: root,
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
if (settings.sizeWarnBytes > 0 && contentBytes > settings.sizeWarnBytes && confirmLarge !== true) {
|
|
992
|
-
throw worktreeError('WORKTREE_LARGE_REPO_CONFIRM', undefined, {
|
|
993
|
-
estimateBytes: contentBytes,
|
|
994
|
-
thresholdBytes: settings.sizeWarnBytes,
|
|
995
|
-
});
|
|
996
|
-
}
|
|
1063
|
+
const { tracked, root, needed } = await assertCheckoutFits({
|
|
1064
|
+
repo,
|
|
1065
|
+
baseCommit,
|
|
1066
|
+
extraBytes: includePlan.copyBytes,
|
|
1067
|
+
settings,
|
|
1068
|
+
confirmLarge,
|
|
1069
|
+
signal,
|
|
1070
|
+
});
|
|
997
1071
|
throwIfAborted(signal);
|
|
998
1072
|
|
|
999
1073
|
const branches = new Set(
|
|
@@ -1085,11 +1159,9 @@ export async function createManagedWorktree({
|
|
|
1085
1159
|
});
|
|
1086
1160
|
throwIfAborted(signal);
|
|
1087
1161
|
record.warnings = [...record.warnings, ...applied.warnings];
|
|
1088
|
-
record.state = 'ready';
|
|
1089
|
-
await putRecord(record);
|
|
1090
1162
|
const made = (record.resources.includes || []).filter((item) => item.status === 'created');
|
|
1091
1163
|
const rels = (predicate) => made.filter(predicate).map((item) => item.rel);
|
|
1092
|
-
// What creation did, for the agent's note at the first turn (transitions.mjs).
|
|
1164
|
+
// What creation did, for the agent's note at the first turn there (transitions.mjs).
|
|
1093
1165
|
const prepared = {
|
|
1094
1166
|
root: repo.topLevel,
|
|
1095
1167
|
copied: rels((item) => item.mode === 'copy'),
|
|
@@ -1097,6 +1169,12 @@ export async function createManagedWorktree({
|
|
|
1097
1169
|
dependencies: linkedDependencies,
|
|
1098
1170
|
skipped: [...includePlan.warnings, ...applied.warnings],
|
|
1099
1171
|
};
|
|
1172
|
+
// Whichever path made it (a worktree task, a branch selection, `git
|
|
1173
|
+
// worktree add`), the first turn a session runs here prepares its
|
|
1174
|
+
// dependencies (claimWorktreeSetup).
|
|
1175
|
+
record.setup = { pending: true, prepared };
|
|
1176
|
+
record.state = 'ready';
|
|
1177
|
+
await putRecord(record);
|
|
1100
1178
|
return { ...entryView(record), prepared, ...(changes ? { changes } : {}) };
|
|
1101
1179
|
} catch (error) {
|
|
1102
1180
|
// Carried already: the changes go back to the project folder before the
|
|
@@ -1158,6 +1236,213 @@ export async function createManagedWorktree({
|
|
|
1158
1236
|
});
|
|
1159
1237
|
}
|
|
1160
1238
|
|
|
1239
|
+
/**
|
|
1240
|
+
* A read-only snapshot: a detached checkout of `commit` (any commit-ish of
|
|
1241
|
+
* the repository) in the managed store, bound to no session. Its record
|
|
1242
|
+
* (`kind: 'snapshot'`, `branch` and `ownerSessionId` null) names the session
|
|
1243
|
+
* whose turn made it (`holderSessionId`, with an owner lease); it is removed
|
|
1244
|
+
* when that turn settles (removeSessionSnapshots), when the session is
|
|
1245
|
+
* deleted, and by the manual prune / startup reconcile once its holder is
|
|
1246
|
+
* gone. No include copies, dependency links or carried changes: a snapshot
|
|
1247
|
+
* only shows the commit. `txId` makes a retry return the same snapshot.
|
|
1248
|
+
* @param {{ projectPath: string, sessionId: string, runtimeToken: string, commit?: string,
|
|
1249
|
+
* txId: string, confirmLarge?: boolean, signal?: AbortSignal }} input
|
|
1250
|
+
*/
|
|
1251
|
+
export async function createSnapshotWorktree({
|
|
1252
|
+
projectPath,
|
|
1253
|
+
sessionId,
|
|
1254
|
+
runtimeToken,
|
|
1255
|
+
commit = 'HEAD',
|
|
1256
|
+
txId,
|
|
1257
|
+
confirmLarge = false,
|
|
1258
|
+
signal,
|
|
1259
|
+
} = {}) {
|
|
1260
|
+
requireText(projectPath, 'projectPath');
|
|
1261
|
+
requireText(txId, 'txId');
|
|
1262
|
+
const holder = normalizeWorktreeOwner({ sessionId, runtimeToken, pid: process.pid });
|
|
1263
|
+
throwIfAborted(signal);
|
|
1264
|
+
const repo = await inspectRepo(projectPath, { signal });
|
|
1265
|
+
return withRepoLock(repo.repoCommonDir, async () => {
|
|
1266
|
+
const prior = findRecord(await readRegistry(), (record) => record.txId === txId);
|
|
1267
|
+
if (prior) {
|
|
1268
|
+
if (prior.kind !== WORKTREE_SNAPSHOT_KIND || prior.holderSessionId !== holder.sessionId) {
|
|
1269
|
+
throw worktreeError('WORKTREE_REVISION_CONFLICT', 'This transaction belongs to another workspace.', { txId });
|
|
1270
|
+
}
|
|
1271
|
+
if (prior.state === 'ready') return entryView(prior);
|
|
1272
|
+
const rollback = await rollbackCreation(prior);
|
|
1273
|
+
if (!rollback.complete) {
|
|
1274
|
+
throw worktreeError('WORKTREE_REVISION_CONFLICT', 'A previous snapshot is still being cleaned up; try again.', {
|
|
1275
|
+
entryId: prior.id,
|
|
1276
|
+
remaining: rollback.remaining,
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const baseCommit = await resolveCommit(repo.projectPath, commit, signal);
|
|
1281
|
+
const settings = await currentWorktreeSettings();
|
|
1282
|
+
const { tracked, root, needed } = await assertCheckoutFits({ repo, baseCommit, settings, confirmLarge, signal });
|
|
1283
|
+
throwIfAborted(signal);
|
|
1284
|
+
let record = null;
|
|
1285
|
+
for (let attempt = 0; attempt < 5 && !record; attempt++) {
|
|
1286
|
+
const ids = new Set(Object.keys((await readRegistry()).entries).map((id) => id.toLowerCase()));
|
|
1287
|
+
const slug = uniqueSlug(
|
|
1288
|
+
`snapshot-${baseCommit.slice(0, 8)}`,
|
|
1289
|
+
(candidate) => ids.has(candidate.toLowerCase()) || existsSync(join(root, candidate))
|
|
1290
|
+
);
|
|
1291
|
+
const candidate = {
|
|
1292
|
+
id: slug,
|
|
1293
|
+
txId,
|
|
1294
|
+
kind: WORKTREE_SNAPSHOT_KIND,
|
|
1295
|
+
repoCommonDir: repo.repoCommonDir,
|
|
1296
|
+
projectPath: repo.projectPath,
|
|
1297
|
+
projectSubdir: projectSubdirOf(repo.topLevel, repo.projectPath),
|
|
1298
|
+
path: managedWorktreePath(repo.repoCommonDir, slug),
|
|
1299
|
+
branch: null,
|
|
1300
|
+
baseCommit,
|
|
1301
|
+
mergeTarget: null,
|
|
1302
|
+
createdAt: Date.now(),
|
|
1303
|
+
ownerSessionId: null,
|
|
1304
|
+
holderSessionId: holder.sessionId,
|
|
1305
|
+
// No branch of its own: nothing is ever deleted from refs/heads.
|
|
1306
|
+
existingBranch: true,
|
|
1307
|
+
exactBranch: false,
|
|
1308
|
+
state: 'creating',
|
|
1309
|
+
disposable: true,
|
|
1310
|
+
warnings: tracked.submodules ? ['submodules'] : [],
|
|
1311
|
+
resources: { worktree: null, lock: null, owner: null, includes: [] },
|
|
1312
|
+
deleting: null,
|
|
1313
|
+
recovery: null,
|
|
1314
|
+
};
|
|
1315
|
+
if (await insertRecord(candidate)) record = candidate;
|
|
1316
|
+
}
|
|
1317
|
+
if (!record) throw worktreeError('WORKTREE_REVISION_CONFLICT', 'Could not reserve a worktree name; try again.');
|
|
1318
|
+
try {
|
|
1319
|
+
await mkdir(root, { recursive: true });
|
|
1320
|
+
await setResource(record, 'worktree', 'pending');
|
|
1321
|
+
await runGit(repo.projectPath, ['worktree', 'add', '--detach', record.path, baseCommit], {
|
|
1322
|
+
signal,
|
|
1323
|
+
timeoutMs: WORKTREE_ADD_TIMEOUT_MS,
|
|
1324
|
+
});
|
|
1325
|
+
await setResource(record, 'worktree', 'created');
|
|
1326
|
+
throwIfAborted(signal);
|
|
1327
|
+
await setResource(record, 'lock', 'pending');
|
|
1328
|
+
await runGit(repo.projectPath, [
|
|
1329
|
+
'worktree',
|
|
1330
|
+
'lock',
|
|
1331
|
+
'--reason',
|
|
1332
|
+
`mixdog snapshot session=${holder.sessionId}`,
|
|
1333
|
+
record.path,
|
|
1334
|
+
]);
|
|
1335
|
+
await setResource(record, 'lock', 'created');
|
|
1336
|
+
await setResource(record, 'owner', 'pending');
|
|
1337
|
+
await writeOwnerLease(record, holder);
|
|
1338
|
+
await setResource(record, 'owner', 'created');
|
|
1339
|
+
record.state = 'ready';
|
|
1340
|
+
await putRecord(record);
|
|
1341
|
+
return entryView(record);
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
const rollback = await rollbackCreation(record, error);
|
|
1344
|
+
const failure = isDiskFullError(error) ? await diskFullFailure(error, { needed, root }) : error;
|
|
1345
|
+
if (!rollback.complete) failure.rollback = rollback;
|
|
1346
|
+
throw failure;
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Hold a snapshot's folder against its consumers on every deletion attempt,
|
|
1353
|
+
* a resumed (`deleting`) one included: WORKTREE_BUSY_JOBS (`folder-in-use`)
|
|
1354
|
+
* while any process's turn (a worker running there) or command (a background
|
|
1355
|
+
* shell, a git tool call) holds a workspace lease inside it; otherwise no new
|
|
1356
|
+
* one is admitted there until the returned release. A folder no longer a
|
|
1357
|
+
* checkout admits no consumer (activity-gate tracks git checkouts only).
|
|
1358
|
+
*/
|
|
1359
|
+
function holdSnapshotFolder(record) {
|
|
1360
|
+
if (!existsSync(join(record.path, '.git'))) return () => {};
|
|
1361
|
+
return holdWorkspaceActivity([record.path]);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/** A refusal that only says "something still runs there" (retry once it ends). */
|
|
1365
|
+
function snapshotInUse(error) {
|
|
1366
|
+
return (
|
|
1367
|
+
error?.code === 'WORKTREE_BUSY_JOBS' &&
|
|
1368
|
+
['folder-in-use', 'workspace-admission-unreadable'].includes(error.details?.reason)
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Remove snapshot `entryId` once nothing runs in it (removeManagedWorktree
|
|
1374
|
+
* holds its folder); changes made in it only with `force`.
|
|
1375
|
+
*/
|
|
1376
|
+
export async function removeSnapshotWorktree({ entryId, force = false, expected = null } = {}) {
|
|
1377
|
+
if (!expected) {
|
|
1378
|
+
const record = await getRecord(entryId);
|
|
1379
|
+
if (record?.kind !== WORKTREE_SNAPSHOT_KIND) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
1380
|
+
}
|
|
1381
|
+
// With `expected`, removeManagedWorktree checks identity (kind included) and skips a stale selection.
|
|
1382
|
+
return removeManagedWorktree({ entryId, force, expected });
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Remove the snapshots `sessionId` holds (`txIds`: only the ones those
|
|
1387
|
+
* creation transactions made — a settled turn's; an id alone could name a
|
|
1388
|
+
* later snapshot reusing a freed slug), changes and all: snapshots are
|
|
1389
|
+
* read-only by contract.
|
|
1390
|
+
* One a worker or background shell still runs in is kept with reason
|
|
1391
|
+
* `in-use` (its caller retries after they end); any other failure stays for
|
|
1392
|
+
* the prune / startup reconcile.
|
|
1393
|
+
* @param {string} sessionId
|
|
1394
|
+
* @param {{ txIds?: string[]|null }} [options]
|
|
1395
|
+
* @returns {Promise<{ removed: string[], kept: Array<{ id: string, txId: string, reason?: 'in-use', error?: string, remaining?: object[] }> }>}
|
|
1396
|
+
*/
|
|
1397
|
+
export async function removeSessionSnapshots(sessionId, { txIds = null } = {}) {
|
|
1398
|
+
const report = { removed: [], kept: [] };
|
|
1399
|
+
if (typeof sessionId !== 'string' || !sessionId) return report;
|
|
1400
|
+
const only = txIds ? new Set(txIds) : null;
|
|
1401
|
+
const held = Object.values((await readRegistry()).entries).filter(
|
|
1402
|
+
(record) =>
|
|
1403
|
+
record.kind === WORKTREE_SNAPSHOT_KIND &&
|
|
1404
|
+
record.holderSessionId === sessionId &&
|
|
1405
|
+
(!only || only.has(record.txId))
|
|
1406
|
+
);
|
|
1407
|
+
for (const record of held) {
|
|
1408
|
+
const row = { id: record.id, txId: record.txId };
|
|
1409
|
+
try {
|
|
1410
|
+
const result = await removeSnapshotWorktree({
|
|
1411
|
+
entryId: record.id,
|
|
1412
|
+
force: true,
|
|
1413
|
+
expected: { kind: record.kind, txId: record.txId, holderSessionId: record.holderSessionId },
|
|
1414
|
+
});
|
|
1415
|
+
// Skipped: the record is no longer the snapshot selected above (its slug was reused).
|
|
1416
|
+
if (result.skipped) continue;
|
|
1417
|
+
if (result.removed) report.removed.push(record.id);
|
|
1418
|
+
else report.kept.push({ ...row, remaining: result.remaining });
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
report.kept.push(
|
|
1421
|
+
snapshotInUse(error) ? { ...row, reason: 'in-use' } : { ...row, error: String(error?.message || error) }
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return report;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* The first turn a session runs in managed worktree `entryId` claims its
|
|
1430
|
+
* dependency preparation, once: `{ prepared }` (what creation did) while it
|
|
1431
|
+
* is still pending, else null. Claimed before it runs, so a failed or
|
|
1432
|
+
* cancelled preparation is never repeated.
|
|
1433
|
+
*/
|
|
1434
|
+
export async function claimWorktreeSetup(entryId) {
|
|
1435
|
+
const record = await getRecord(entryId);
|
|
1436
|
+
if (!record?.setup?.pending) return null;
|
|
1437
|
+
return withRepoLock(record.repoCommonDir, async () => {
|
|
1438
|
+
const current = await getRecord(entryId);
|
|
1439
|
+
if (current?.state !== 'ready' || !current.setup?.pending) return null;
|
|
1440
|
+
current.setup = { ...current.setup, pending: false, claimedAt: Date.now() };
|
|
1441
|
+
await putRecord(current);
|
|
1442
|
+
return { prepared: current.setup.prepared ?? null };
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1161
1446
|
/**
|
|
1162
1447
|
* After a carry whose session move failed: put the changes kept at
|
|
1163
1448
|
* `recoveryRef` back into the project folder (refused, ref kept, when the
|
|
@@ -1538,8 +1823,11 @@ export async function assertWorktreeRemovable({ entryId, force = false, sessionI
|
|
|
1538
1823
|
* Remove a managed worktree. Refuses dirty (incl. untracked and modified
|
|
1539
1824
|
* include copies) or unmerged worktrees unless `force`. Retained file recovery
|
|
1540
1825
|
* data blocks removal regardless of force.
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1826
|
+
* `expected` ({ kind, txId, holderSessionId }) pins the record the caller
|
|
1827
|
+
* selected: one that no longer matches under the repository lock (removed,
|
|
1828
|
+
* its slug reused) is left alone and reported `{ removed: false, skipped: true }`.
|
|
1829
|
+
* @param {{ entryId: string, force?: boolean, expected?: { kind?: string, txId?: string, holderSessionId?: string }|null }} input
|
|
1830
|
+
* @returns {Promise<{ removed: boolean, skipped?: boolean, retryable?: boolean, remaining?: object[], branchDeleted?: boolean }>}
|
|
1543
1831
|
*/
|
|
1544
1832
|
export async function removeManagedWorktree({
|
|
1545
1833
|
entryId,
|
|
@@ -1547,29 +1835,54 @@ export async function removeManagedWorktree({
|
|
|
1547
1835
|
sessionId,
|
|
1548
1836
|
preserveBranch = false,
|
|
1549
1837
|
safeOnly = false,
|
|
1838
|
+
expected = null,
|
|
1550
1839
|
} = {}) {
|
|
1840
|
+
const skipped = { removed: false, skipped: true };
|
|
1841
|
+
const matches = (record) =>
|
|
1842
|
+
!expected ||
|
|
1843
|
+
(record.kind === expected.kind &&
|
|
1844
|
+
record.txId === expected.txId &&
|
|
1845
|
+
record.holderSessionId === expected.holderSessionId);
|
|
1551
1846
|
const record = await getRecord(entryId);
|
|
1552
|
-
if (!record)
|
|
1847
|
+
if (!record) {
|
|
1848
|
+
if (expected) return skipped;
|
|
1849
|
+
throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
1850
|
+
}
|
|
1553
1851
|
return withRepoLock(record.repoCommonDir, async () => {
|
|
1554
1852
|
const current = await getRecord(entryId);
|
|
1555
|
-
if (!current
|
|
1853
|
+
if (!current || !matches(current)) {
|
|
1854
|
+
if (!current && !expected) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
|
|
1855
|
+
return skipped;
|
|
1856
|
+
}
|
|
1556
1857
|
assertNotShared(current, sessionId);
|
|
1557
1858
|
if (current.state === 'creating' || current.state === 'recovery') {
|
|
1558
1859
|
const rollback = await rollbackCreation(current);
|
|
1559
1860
|
return rollback.complete ? { removed: true } : { removed: false, retryable: true, remaining: rollback.remaining };
|
|
1560
1861
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
current
|
|
1566
|
-
|
|
1567
|
-
|
|
1862
|
+
// Every deletion attempt of a snapshot, a resumed one included, waits
|
|
1863
|
+
// for (refuses on) its consumers.
|
|
1864
|
+
const release = current.kind === WORKTREE_SNAPSHOT_KIND ? holdSnapshotFolder(current) : () => {};
|
|
1865
|
+
try {
|
|
1866
|
+
return await decideAndFinishDeletion(current, { force, preserveBranch, safeOnly });
|
|
1867
|
+
} finally {
|
|
1868
|
+
release();
|
|
1568
1869
|
}
|
|
1569
|
-
return finishDeletion(current);
|
|
1570
1870
|
});
|
|
1571
1871
|
}
|
|
1572
1872
|
|
|
1873
|
+
/** Mark a ready worktree `deleting` (after its guards), then finish the deletion. */
|
|
1874
|
+
async function decideAndFinishDeletion(current, { force, preserveBranch, safeOnly }) {
|
|
1875
|
+
if (current.state === 'ready') {
|
|
1876
|
+
await assertRecoveryStoresClear(current);
|
|
1877
|
+
await assertManagedCheckoutPath(current);
|
|
1878
|
+
if (!force) await assertRemovable(current);
|
|
1879
|
+
current.state = 'deleting';
|
|
1880
|
+
current.deleting = { force: force === true, preserveBranch, safeOnly, startedAt: Date.now(), remaining: [] };
|
|
1881
|
+
await putRecord(current);
|
|
1882
|
+
}
|
|
1883
|
+
return finishDeletion(current);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1573
1886
|
/** A lease whose recorded process still runs. */
|
|
1574
1887
|
function leaseProcessAlive(lease) {
|
|
1575
1888
|
return !!lease?.pid && config.processAlive(lease.pid);
|
|
@@ -1587,12 +1900,17 @@ export async function listManagedWorktrees({ liveSessionIds = [], retainedSessio
|
|
|
1587
1900
|
const retained = new Set(retainedSessionIds);
|
|
1588
1901
|
const rows = [];
|
|
1589
1902
|
for (const record of Object.values((await readRegistry()).entries)) {
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1903
|
+
const snapshot = record.kind === WORKTREE_SNAPSHOT_KIND;
|
|
1904
|
+
const ownerLive = snapshot
|
|
1905
|
+
? await snapshotHolderLive(record, live)
|
|
1906
|
+
: (!!record.ownerSessionId && live.has(record.ownerSessionId)) ||
|
|
1907
|
+
leaseProcessAlive(await readOwnerLease(record));
|
|
1592
1908
|
const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1909
|
+
// A snapshot outlives no holder: the prune removes it, changes and all.
|
|
1910
|
+
let removable = snapshot
|
|
1911
|
+
? !ownerLive
|
|
1912
|
+
: !record.sharedSessionIds?.length &&
|
|
1913
|
+
(record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained)));
|
|
1596
1914
|
if (removable) {
|
|
1597
1915
|
try {
|
|
1598
1916
|
await assertRecoveryStoresClear(record);
|
|
@@ -1620,13 +1938,16 @@ export async function listManagedWorktrees({ liveSessionIds = [], retainedSessio
|
|
|
1620
1938
|
* autoCleanup setting at startup; true for a manual prune) also removes
|
|
1621
1939
|
* clean, merged worktrees that no stored session retains (or that a deleted
|
|
1622
1940
|
* session left marked disposable) and whose owner is not live — never one a
|
|
1623
|
-
* session in the store still references.
|
|
1624
|
-
*
|
|
1941
|
+
* session in the store still references. A snapshot whose holder is neither
|
|
1942
|
+
* live nor running in another process is removed in either mode.
|
|
1943
|
+
* `repoCommonDir` limits the whole reconcile to that repository.
|
|
1944
|
+
* @param {{ liveSessionIds?: string[], retainedSessionIds?: string[], removeOrphans?: boolean, repoCommonDir?: string|null }} input
|
|
1625
1945
|
*/
|
|
1626
1946
|
export async function reconcileManagedWorktrees({
|
|
1627
1947
|
liveSessionIds = [],
|
|
1628
1948
|
retainedSessionIds = [],
|
|
1629
1949
|
removeOrphans = false,
|
|
1950
|
+
repoCommonDir: onlyRepo = null,
|
|
1630
1951
|
} = {}) {
|
|
1631
1952
|
const live = new Set(liveSessionIds);
|
|
1632
1953
|
const retained = new Set(retainedSessionIds);
|
|
@@ -1638,6 +1959,7 @@ export async function reconcileManagedWorktrees({
|
|
|
1638
1959
|
const byRepo = new Map();
|
|
1639
1960
|
for (const record of Object.values((await readRegistry()).entries)) {
|
|
1640
1961
|
const key = pathKey(record.repoCommonDir);
|
|
1962
|
+
if (onlyRepo && key !== pathKey(onlyRepo)) continue;
|
|
1641
1963
|
if (!byRepo.has(key)) byRepo.set(key, record.repoCommonDir);
|
|
1642
1964
|
}
|
|
1643
1965
|
for (const [key, repoCommonDir] of byRepo) {
|
|
@@ -1668,11 +1990,56 @@ export async function reconcileManagedWorktrees({
|
|
|
1668
1990
|
else report.kept.push({ id: record.id, reason: 'recovery', remaining: result.remaining });
|
|
1669
1991
|
continue;
|
|
1670
1992
|
}
|
|
1993
|
+
if (record.state === 'deleting' && record.kind === WORKTREE_SNAPSHOT_KIND) {
|
|
1994
|
+
// An unfinished snapshot deletion: whatever entered the
|
|
1995
|
+
// surviving folder since still keeps it.
|
|
1996
|
+
let release;
|
|
1997
|
+
try {
|
|
1998
|
+
release = holdSnapshotFolder(record);
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
if (!snapshotInUse(error)) throw error;
|
|
2001
|
+
report.kept.push({ id: record.id, reason: 'snapshot-in-use' });
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
try {
|
|
2005
|
+
reportDeletion(record, await finishDeletion(record));
|
|
2006
|
+
} finally {
|
|
2007
|
+
release();
|
|
2008
|
+
}
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
1671
2011
|
if (record.state === 'deleting') {
|
|
1672
2012
|
// A decided deletion: its session already left the folder.
|
|
1673
2013
|
reportDeletion(record, await finishDeletion(record));
|
|
1674
2014
|
continue;
|
|
1675
2015
|
}
|
|
2016
|
+
if (record.kind === WORKTREE_SNAPSHOT_KIND) {
|
|
2017
|
+
// Read-only and turn-scoped: once its holder is gone it goes,
|
|
2018
|
+
// whatever autoCleanup says and whatever was changed in it.
|
|
2019
|
+
// A worker or background shell still running in it keeps it too.
|
|
2020
|
+
let release = null;
|
|
2021
|
+
if (!(await snapshotHolderLive(record, live))) {
|
|
2022
|
+
try {
|
|
2023
|
+
release = holdSnapshotFolder(record);
|
|
2024
|
+
} catch (error) {
|
|
2025
|
+
if (!snapshotInUse(error)) throw error;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
if (!release) {
|
|
2029
|
+
report.kept.push({ id: record.id, reason: 'snapshot-in-use' });
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
try {
|
|
2033
|
+
await assertRecoveryStoresClear(record);
|
|
2034
|
+
record.state = 'deleting';
|
|
2035
|
+
record.deleting = { force: true, startedAt: Date.now(), remaining: [] };
|
|
2036
|
+
await putRecord(record);
|
|
2037
|
+
reportDeletion(record, await finishDeletion(record));
|
|
2038
|
+
} finally {
|
|
2039
|
+
release();
|
|
2040
|
+
}
|
|
2041
|
+
continue;
|
|
2042
|
+
}
|
|
1676
2043
|
const lease = await readOwnerLease(record);
|
|
1677
2044
|
const ownerLive = (!!record.ownerSessionId && live.has(record.ownerSessionId)) || leaseProcessAlive(lease);
|
|
1678
2045
|
if (!ownerLive && lease) {
|