mixdog 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +183 -63
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +91 -66
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +124 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -5
- package/scripts/suite-health.mjs +115 -24
- package/scripts/test-data-dir.mjs +10 -7
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/app.mjs +4 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +23 -2
- package/src/defaults/skills/browser-use/SKILL.md +10 -5
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +16 -4
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +35 -42
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +12 -7
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +8 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +29 -11
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +11 -2
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +113 -36
- package/src/runtime/shared/tool-work-units.mjs +6 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +72 -0
- package/src/runtime/shared/worktrees/manager.mjs +285 -168
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +6 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +458 -312
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +7 -1
- package/src/session-runtime/bot-status.mjs +107 -101
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +401 -36
- package/src/session-runtime/conversation-import/service.mjs +35 -8
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +283 -11
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1041
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +169 -138
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +10 -4
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +29 -18
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1963 -1361
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/ui/statusline-agents.mjs +1 -3
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// The admission thread (activity-gate.mjs): runs the ledger operations its
|
|
2
|
+
// starter posts, one at a time and in the order they were posted. A request it
|
|
3
|
+
// answers is complete — both copies written, or refused — and nothing here
|
|
4
|
+
// ever waits for the thread that posted it.
|
|
5
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
6
|
+
import { LEDGER_SLOTS as SLOT, admit, hold, release } from './activity-ledger.mjs';
|
|
7
|
+
|
|
8
|
+
const slots = new Int32Array(workerData.slots);
|
|
9
|
+
const { syncPort } = workerData;
|
|
10
|
+
const operations = { admit, hold, release };
|
|
11
|
+
|
|
12
|
+
// Fault injection for tests only (MIXDOG_ADMISSION_TEST_FAULT_HOOK=1): the
|
|
13
|
+
// one fixed test-support module beside this file, loaded before the first
|
|
14
|
+
// request is taken. It is not shipped (package.json `files`), so in an
|
|
15
|
+
// installed runtime the flag does nothing.
|
|
16
|
+
if (workerData.faultHook) {
|
|
17
|
+
try {
|
|
18
|
+
await import(new URL('./activity-gate-faults.test-hook.mjs', import.meta.url).href);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error?.code !== 'ERR_MODULE_NOT_FOUND') throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
process.on('exit', () => {
|
|
25
|
+
Atomics.store(slots, SLOT.STOPPED, 1);
|
|
26
|
+
Atomics.notify(slots, SLOT.ANSWERED);
|
|
27
|
+
Atomics.notify(slots, SLOT.DONE);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const serialize = (error) => ({
|
|
31
|
+
name: error?.name,
|
|
32
|
+
message: String(error?.message ?? error),
|
|
33
|
+
code: error?.code,
|
|
34
|
+
details: error?.details,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
parentPort.on('message', ({ id, op, args, sync }) => {
|
|
38
|
+
let answer;
|
|
39
|
+
try {
|
|
40
|
+
answer = { id, value: operations[op](args) };
|
|
41
|
+
} catch (error) {
|
|
42
|
+
answer = { id, error: serialize(error) };
|
|
43
|
+
}
|
|
44
|
+
if (!sync) parentPort.postMessage(answer);
|
|
45
|
+
else if (Atomics.compareExchange(slots, SLOT.SYNC, id, -id) === id) {
|
|
46
|
+
syncPort.postMessage(answer);
|
|
47
|
+
Atomics.add(slots, SLOT.ANSWERED, 1);
|
|
48
|
+
Atomics.notify(slots, SLOT.ANSWERED);
|
|
49
|
+
} else if (op !== 'release' && answer.value) {
|
|
50
|
+
// Its caller stopped waiting and reported the outcome unknown: nobody
|
|
51
|
+
// holds this grant, so it is withdrawn here.
|
|
52
|
+
release(answer.value);
|
|
53
|
+
}
|
|
54
|
+
Atomics.add(slots, SLOT.DONE, 1);
|
|
55
|
+
Atomics.notify(slots, SLOT.DONE);
|
|
56
|
+
});
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The workspace admission ledger. Only the admission thread runs the ledger
|
|
3
|
+
* operations below (activity-ledger-worker.mjs, started by activity-gate.mjs):
|
|
4
|
+
* it owns this module's bookkeeping (`held`, `pending`, the heal timer) and
|
|
5
|
+
* runs one operation at a time, in the order requested. Every other thread
|
|
6
|
+
* of the process asks it through activity-gate.mjs.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { isAbsolute, join } from 'node:path';
|
|
11
|
+
import { parseJsonText, quarantinePath, withFileLockSync, writeFileAtomicSync } from '../atomic-file.mjs';
|
|
12
|
+
import { isPidAliveOrUnknown } from '../pid-liveness.mjs';
|
|
13
|
+
import { findGitLayout } from './git-head.mjs';
|
|
14
|
+
import { canonicalPath, isWithin, samePath } from './paths.mjs';
|
|
15
|
+
import { worktreeError, worktreeRefusal } from './contract.mjs';
|
|
16
|
+
import { readRegistrySync } from './registry.mjs';
|
|
17
|
+
|
|
18
|
+
const FILE = 'mixdog-workspace-admission.json';
|
|
19
|
+
const overlaps = (a, b) => isWithin(a, b) || isWithin(b, a);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The Int32 slots the admission thread shares with the thread that started
|
|
23
|
+
* it. SYNC: the id of the synchronous request its caller waits for; the
|
|
24
|
+
* admission thread swaps it to -id when it answers, the caller to 0 when it
|
|
25
|
+
* gives up — whichever comes first decides. ANSWERED counts synchronous
|
|
26
|
+
* answers, DONE every finished request, STOPPED is set once the admission
|
|
27
|
+
* thread is gone.
|
|
28
|
+
*/
|
|
29
|
+
export const LEDGER_SLOTS = Object.freeze({ SYNC: 0, ANSWERED: 1, DONE: 2, STOPPED: 3, COUNT: 4 });
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The admission policy's external exception: a linked checkout Mixdog did not
|
|
33
|
+
* create (not a project's main folder, not a registered managed worktree) is
|
|
34
|
+
* not lock-blocked by what is held elsewhere. Management takes no lease on it
|
|
35
|
+
* and its readers are admitted whatever is held outside it; its running
|
|
36
|
+
* readers still refuse a management action that touches it. A management lease
|
|
37
|
+
* published inside the checkout itself (another profile, or a registry
|
|
38
|
+
* recovered empty) still refuses its readers.
|
|
39
|
+
*/
|
|
40
|
+
export function isExternalCheckout(folder) {
|
|
41
|
+
const layout = findGitLayout(folder, { strict: true });
|
|
42
|
+
if (!layout || samePath(layout.gitDir, layout.commonDir)) return false;
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = readRegistrySync().entries;
|
|
46
|
+
} catch {
|
|
47
|
+
// A damaged registry is kept aside and reads as a restored or empty one;
|
|
48
|
+
// only a real I/O failure (access denied, in use) lands here.
|
|
49
|
+
throw worktreeRefusal('workspace-admission-unreadable');
|
|
50
|
+
}
|
|
51
|
+
return !Object.values(entries).some(
|
|
52
|
+
(record) => typeof record?.path === 'string' && samePath(record.path, layout.topLevel)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* The admission ledger lists every turn, command and management lease of a
|
|
58
|
+
* repository. It is kept in two copies with the same content in the Git
|
|
59
|
+
* common directory: `mixdog-workspace-admission.json` and its `.management`
|
|
60
|
+
* mirror. A grant returns its release only once both copies hold it, and a
|
|
61
|
+
* release is forgotten only once both copies dropped it, so damage to either
|
|
62
|
+
* copy alone never loses an acknowledged lease. `recoveredAt` marks a ledger
|
|
63
|
+
* that may be missing leases because no complete copy survived: readers still
|
|
64
|
+
* run, management refuses, and only a quiescent reset that removes both
|
|
65
|
+
* copies clears it (docs/git-github-integration.md). Time, healing and
|
|
66
|
+
* releases never do.
|
|
67
|
+
*/
|
|
68
|
+
const VERSION = 2;
|
|
69
|
+
const isLease = (lease) =>
|
|
70
|
+
!!lease &&
|
|
71
|
+
['turn', 'command', 'management'].includes(lease.kind) &&
|
|
72
|
+
typeof lease.token === 'string' &&
|
|
73
|
+
Number.isSafeInteger(lease.pid) &&
|
|
74
|
+
lease.pid > 0 &&
|
|
75
|
+
typeof lease.path === 'string' &&
|
|
76
|
+
isAbsolute(lease.path);
|
|
77
|
+
// Only a pid PROVEN dead (ESRCH) drops its lease, whatever its kind; an
|
|
78
|
+
// unprobeable pid still counts as running. A live runtime keeps a command
|
|
79
|
+
// whose tree it cannot verify reserved under its own (live) pid until the
|
|
80
|
+
// tree is confirmed gone, so a lease that outlives every pid belongs to a
|
|
81
|
+
// restarted runtime that can no longer release it.
|
|
82
|
+
const aliveLease = (lease) => isPidAliveOrUnknown(lease.pid);
|
|
83
|
+
// A management hold shares one token across its folders, so a lease is
|
|
84
|
+
// identified by token and path.
|
|
85
|
+
const leaseKey = (lease) => `${lease.token}\0${lease.path}`;
|
|
86
|
+
const unverifiable = () => worktreeRefusal('workspace-admission-unreadable');
|
|
87
|
+
const copyPaths = (commonDir) => {
|
|
88
|
+
const primary = join(commonDir, FILE);
|
|
89
|
+
return [primary, `${primary}.management`];
|
|
90
|
+
};
|
|
91
|
+
export const envMs = (name, fallback) => {
|
|
92
|
+
const value = Number(process.env[name]);
|
|
93
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
94
|
+
};
|
|
95
|
+
// How often the held leases are checked against both copies: a retry period
|
|
96
|
+
// for repair, never a lease decision (liveness is the pid probe, aliveLease).
|
|
97
|
+
const healIntervalMs = () => envMs('MIXDOG_ADMISSION_HEAL_MS', 2_000);
|
|
98
|
+
|
|
99
|
+
// The leases this process holds, put back into a ledger that lost them, and
|
|
100
|
+
// the tokens it released or failed to grant whose removal from both copies is
|
|
101
|
+
// not confirmed yet (token -> common dir), retried until it is.
|
|
102
|
+
const held = new Map();
|
|
103
|
+
const pending = new Map();
|
|
104
|
+
let healTimer = null;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* One copy as read under the ledger lock. A copy that cannot be read (access
|
|
108
|
+
* denied, in use, a directory) or that a newer build wrote refuses everything
|
|
109
|
+
* and is never written: neither may pass for an empty ledger. Anything else
|
|
110
|
+
* that is not a ledger, or holds an invalid lease record, is damaged; its
|
|
111
|
+
* valid records still count. A copy is complete when it accounts for every
|
|
112
|
+
* acknowledged lease: intact, not uncertain, and version 2 — or the version 1
|
|
113
|
+
* primary, since the version 1 mirror held management leases only.
|
|
114
|
+
*/
|
|
115
|
+
function readCopy(path, primary) {
|
|
116
|
+
let bytes;
|
|
117
|
+
try {
|
|
118
|
+
bytes = readFileSync(path);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error?.code === 'ENOENT') return { path, leases: [] };
|
|
121
|
+
throw unverifiable();
|
|
122
|
+
}
|
|
123
|
+
let ledger = null;
|
|
124
|
+
try {
|
|
125
|
+
ledger = parseJsonText(bytes.toString('utf8'));
|
|
126
|
+
} catch {
|
|
127
|
+
// Unparseable: damaged below.
|
|
128
|
+
}
|
|
129
|
+
if (Number.isSafeInteger(ledger?.version) && ledger.version > VERSION) throw unverifiable();
|
|
130
|
+
if (![1, VERSION].includes(ledger?.version) || !Array.isArray(ledger.leases)) {
|
|
131
|
+
return { path, bytes, leases: [], damaged: true };
|
|
132
|
+
}
|
|
133
|
+
const leases = ledger.leases.filter(isLease);
|
|
134
|
+
const damaged = leases.length !== ledger.leases.length;
|
|
135
|
+
const uncertain = Object.hasOwn(ledger, 'recoveredAt');
|
|
136
|
+
const complete = !damaged && !uncertain && (ledger.version === VERSION || primary);
|
|
137
|
+
return { path, bytes, leases, damaged, uncertain, complete, recoveredAt: ledger.recoveredAt };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The ledger recovered from both copies: the union of their valid lease
|
|
142
|
+
* records (one copy lacking a lease never drops it), less proven-dead pids
|
|
143
|
+
* and this process's pending removals, plus the leases this process holds.
|
|
144
|
+
* It stays uncertain when either copy is, and becomes so when a copy exists
|
|
145
|
+
* but none is complete. Both copies missing reads as a new repository.
|
|
146
|
+
*/
|
|
147
|
+
function recover(commonDir, copies) {
|
|
148
|
+
const own = [...held.values()].filter((entry) => entry.commonDir === commonDir).map((entry) => entry.lease);
|
|
149
|
+
const leases = new Map();
|
|
150
|
+
for (const lease of [...copies.flatMap((copy) => copy.leases), ...own]) {
|
|
151
|
+
const key = leaseKey(lease);
|
|
152
|
+
if (!leases.has(key) && pending.get(lease.token) !== commonDir && aliveLease(lease)) leases.set(key, lease);
|
|
153
|
+
}
|
|
154
|
+
const ledger = { version: VERSION, leases: [...leases.values()] };
|
|
155
|
+
const uncertain =
|
|
156
|
+
copies.some((copy) => copy.uncertain) ||
|
|
157
|
+
(copies.some((copy) => copy.bytes) && !copies.some((copy) => copy.complete));
|
|
158
|
+
if (uncertain) ledger.recoveredAt = copies.map((copy) => copy.recoveredAt).find(Number.isFinite) ?? Date.now();
|
|
159
|
+
return ledger;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* #117: the bytes of a damaged copy are copied aside as `<copy>.corrupt-<ts>`
|
|
164
|
+
* before it is rewritten. If they cannot be kept, nothing is written and
|
|
165
|
+
* everything refuses.
|
|
166
|
+
*/
|
|
167
|
+
function keepDamagedAside(copies) {
|
|
168
|
+
for (const copy of copies.filter((each) => each.damaged)) {
|
|
169
|
+
const aside = quarantinePath(copy.path);
|
|
170
|
+
try {
|
|
171
|
+
writeFileAtomicSync(aside, copy.bytes);
|
|
172
|
+
} catch {
|
|
173
|
+
throw unverifiable();
|
|
174
|
+
}
|
|
175
|
+
process.stderr.write(`[state] ${copy.path} is damaged; kept as ${aside}\n`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Writes the primary, then the mirror; a copy that already holds the ledger is left alone. */
|
|
180
|
+
function publish(copies, ledger) {
|
|
181
|
+
const text = Buffer.from(`${JSON.stringify(ledger, null, 2)}\n`);
|
|
182
|
+
for (const copy of copies) {
|
|
183
|
+
if (copy.bytes?.equals(text)) continue;
|
|
184
|
+
copy.bytes = null; // Unknown until the write lands.
|
|
185
|
+
writeFileAtomicSync(copy.path, text);
|
|
186
|
+
copy.bytes = text;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function settle(tokens) {
|
|
191
|
+
for (const token of tokens) pending.delete(token);
|
|
192
|
+
syncHealTimer();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Runs `update(leases, { unknown })` on the recovered ledger under the
|
|
197
|
+
* primary copy's lock and publishes the result to both copies before
|
|
198
|
+
* returning. `unknown`: the ledger may be missing leases. A refused update
|
|
199
|
+
* still publishes the repaired ledger. A grant that cannot be published is
|
|
200
|
+
* refused: its leases become pending removals and the ledger without them is
|
|
201
|
+
* published again; whatever that leaves behind is withdrawn by the next
|
|
202
|
+
* mutation or heal tick, which also blocks every later grant until it lands.
|
|
203
|
+
*/
|
|
204
|
+
function mutate(commonDir, update = () => {}) {
|
|
205
|
+
const paths = copyPaths(commonDir);
|
|
206
|
+
return withFileLockSync(
|
|
207
|
+
`${paths[0]}.lock`,
|
|
208
|
+
() => {
|
|
209
|
+
const copies = paths.map((path, index) => readCopy(path, index === 0));
|
|
210
|
+
keepDamagedAside(copies);
|
|
211
|
+
const withdrawn = [...pending].filter(([, dir]) => dir === commonDir).map(([token]) => token);
|
|
212
|
+
const base = recover(commonDir, copies);
|
|
213
|
+
const leases = [...base.leases];
|
|
214
|
+
let result;
|
|
215
|
+
let refusal = null;
|
|
216
|
+
try {
|
|
217
|
+
result = update(leases, { unknown: Object.hasOwn(base, 'recoveredAt') });
|
|
218
|
+
} catch (error) {
|
|
219
|
+
refusal = error;
|
|
220
|
+
}
|
|
221
|
+
const granted = refusal ? [] : leases.filter((lease) => !base.leases.includes(lease));
|
|
222
|
+
try {
|
|
223
|
+
publish(copies, refusal ? base : { ...base, leases });
|
|
224
|
+
settle(withdrawn);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
process.stderr.write(`[state] ${paths[0]} could not be published (${error?.message})\n`);
|
|
227
|
+
if (refusal) throw refusal;
|
|
228
|
+
for (const lease of granted) pending.set(lease.token, commonDir);
|
|
229
|
+
if (granted.length) {
|
|
230
|
+
try {
|
|
231
|
+
publish(copies, base);
|
|
232
|
+
settle([...withdrawn, ...granted.map((lease) => lease.token)]);
|
|
233
|
+
} catch {
|
|
234
|
+
syncHealTimer();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
throw unverifiable();
|
|
238
|
+
}
|
|
239
|
+
if (refusal) throw refusal;
|
|
240
|
+
return result;
|
|
241
|
+
},
|
|
242
|
+
{ strictOwner: true }
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Without the lock, a cheap check that both copies are the same current
|
|
247
|
+
// ledger holding every live lease this process holds there and none of the
|
|
248
|
+
// tokens it is withdrawing. Repair, never proof that the ledger is complete.
|
|
249
|
+
function inSync(commonDir) {
|
|
250
|
+
let ledger;
|
|
251
|
+
try {
|
|
252
|
+
const [primary, mirror] = copyPaths(commonDir).map((path) => readFileSync(path));
|
|
253
|
+
if (!primary.equals(mirror)) return false;
|
|
254
|
+
ledger = parseJsonText(primary.toString('utf8'));
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (ledger?.version !== VERSION || !Array.isArray(ledger.leases)) return false;
|
|
259
|
+
const stored = new Set(ledger.leases.filter(isLease).map(leaseKey));
|
|
260
|
+
return (
|
|
261
|
+
[...held].every(([key, entry]) => entry.commonDir !== commonDir || stored.has(key) || !aliveLease(entry.lease)) &&
|
|
262
|
+
!ledger.leases.some((lease) => pending.get(lease?.token) === commonDir)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function healHeld() {
|
|
267
|
+
const dirs = new Set([...[...held.values()].map((entry) => entry.commonDir), ...pending.values()]);
|
|
268
|
+
for (const commonDir of dirs) {
|
|
269
|
+
if (inSync(commonDir)) continue;
|
|
270
|
+
try {
|
|
271
|
+
mutate(commonDir);
|
|
272
|
+
} catch {
|
|
273
|
+
// Retried on the next tick.
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
syncHealTimer();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function syncHealTimer() {
|
|
280
|
+
if (held.size || pending.size) {
|
|
281
|
+
healTimer ??= setInterval(healHeld, healIntervalMs());
|
|
282
|
+
healTimer.unref?.();
|
|
283
|
+
} else if (healTimer) {
|
|
284
|
+
clearInterval(healTimer);
|
|
285
|
+
healTimer = null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function track(commonDir, lease) {
|
|
290
|
+
held.set(leaseKey(lease), { commonDir, lease });
|
|
291
|
+
syncHealTimer();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Forgotten as held first, so nothing puts the lease back; its token stays
|
|
295
|
+
// pending until both copies dropped it. Never throws: a removal that did not
|
|
296
|
+
// land is retried by the next mutation or heal tick.
|
|
297
|
+
function releaseLease({ commonDir, token }) {
|
|
298
|
+
for (const [key, entry] of held) if (entry.lease.token === token) held.delete(key);
|
|
299
|
+
pending.set(token, commonDir);
|
|
300
|
+
try {
|
|
301
|
+
mutate(commonDir);
|
|
302
|
+
} catch {
|
|
303
|
+
// Retried by the next mutation or heal tick.
|
|
304
|
+
}
|
|
305
|
+
syncHealTimer();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Releases the handles a grant returned, last granted first (one per
|
|
310
|
+
* repository the grant touched).
|
|
311
|
+
*/
|
|
312
|
+
export function release({ leases }) {
|
|
313
|
+
for (const handle of [...leases].reverse()) releaseLease(handle);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Readers share a checkout freely. Null means a management writer currently
|
|
318
|
+
* owns it; a reader in an external checkout is admitted whatever is held
|
|
319
|
+
* outside it, but refused by a management lease inside it. Otherwise the
|
|
320
|
+
* handles to release (none outside a Git checkout). Throws
|
|
321
|
+
* `workspace-admission-unreadable` when the ledger cannot be read, was
|
|
322
|
+
* written by a newer build, or the grant could not be made durable in both
|
|
323
|
+
* copies: no work may run unrecorded.
|
|
324
|
+
*/
|
|
325
|
+
export function admit({ sessionId, cwd, pid = process.pid, kind = 'turn' }) {
|
|
326
|
+
const layout = findGitLayout(cwd, { strict: true });
|
|
327
|
+
if (!layout) return { leases: [] };
|
|
328
|
+
const external = isExternalCheckout(cwd);
|
|
329
|
+
const path = canonicalPath(cwd);
|
|
330
|
+
const token = randomUUID();
|
|
331
|
+
const lease = { kind, sessionId, pid, token, path };
|
|
332
|
+
// External is this profile's registry view: management another profile (or
|
|
333
|
+
// a registry recovered empty) published inside this very checkout still holds.
|
|
334
|
+
const checkout = canonicalPath(layout.topLevel);
|
|
335
|
+
const holds = (other) => (external ? isWithin(checkout, other.path) : overlaps(other.path, path));
|
|
336
|
+
const admitted = mutate(layout.commonDir, (leases) => {
|
|
337
|
+
if (leases.some((other) => other.kind === 'management' && holds(other))) return false;
|
|
338
|
+
leases.push(lease);
|
|
339
|
+
return true;
|
|
340
|
+
});
|
|
341
|
+
if (!admitted) return null;
|
|
342
|
+
track(layout.commonDir, lease);
|
|
343
|
+
return { leases: [{ commonDir: layout.commonDir, token }] };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Writers refuse while any process's reader overlaps an affected folder, then
|
|
348
|
+
* exclude later readers until the returned handles are released. External
|
|
349
|
+
* checkouts are only checked, never held. `sessionId`: the acting session,
|
|
350
|
+
* whose own readers in this process do not count (its idleness is the
|
|
351
|
+
* caller's check).
|
|
352
|
+
*/
|
|
353
|
+
export function hold({ folders, sessionId = null }) {
|
|
354
|
+
const groups = new Map();
|
|
355
|
+
for (const folder of folders) {
|
|
356
|
+
const layout = findGitLayout(folder, { strict: true });
|
|
357
|
+
if (!layout) throw worktreeError('WORKTREE_UNSUPPORTED_REPO');
|
|
358
|
+
const paths = groups.get(layout.commonDir) ?? [];
|
|
359
|
+
paths.push({ path: canonicalPath(folder), external: isExternalCheckout(folder) });
|
|
360
|
+
groups.set(layout.commonDir, paths);
|
|
361
|
+
}
|
|
362
|
+
const handles = [];
|
|
363
|
+
try {
|
|
364
|
+
for (const [commonDir, paths] of [...groups].sort(([a], [b]) => a.localeCompare(b))) {
|
|
365
|
+
const token = randomUUID();
|
|
366
|
+
const taken = [];
|
|
367
|
+
mutate(commonDir, (leases, { unknown }) => {
|
|
368
|
+
const conflicts = leases.filter(
|
|
369
|
+
(lease) =>
|
|
370
|
+
!(sessionId && lease.sessionId === sessionId && lease.pid === process.pid) &&
|
|
371
|
+
paths.some(({ path }) => overlaps(path, lease.path))
|
|
372
|
+
);
|
|
373
|
+
if (conflicts.length) throw worktreeRefusal('folder-in-use');
|
|
374
|
+
// The ledger may be missing a live foreign lease: refused until reset.
|
|
375
|
+
if (unknown) throw unverifiable();
|
|
376
|
+
for (const { path, external } of paths) {
|
|
377
|
+
if (external) continue;
|
|
378
|
+
const lease = { kind: 'management', pid: process.pid, token, path };
|
|
379
|
+
leases.push(lease);
|
|
380
|
+
taken.push(lease);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
for (const lease of taken) track(commonDir, lease);
|
|
384
|
+
handles.push({ commonDir, token });
|
|
385
|
+
}
|
|
386
|
+
} catch (error) {
|
|
387
|
+
release({ leases: handles });
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
return { leases: handles };
|
|
391
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { branchCommit, inspectRepo, listWorktrees, runGit, validBranchName } from './git.mjs';
|
|
2
|
+
import { branchCommit, branchCommitPair, inspectRepo, listWorktrees, runGit, validBranchName } from './git.mjs';
|
|
3
3
|
import { worktreeError, worktreeRefusal } from './contract.mjs';
|
|
4
4
|
import { samePath } from './paths.mjs';
|
|
5
5
|
import { getRecord, putRecord, readRegistry } from './registry.mjs';
|
|
@@ -37,7 +37,9 @@ function assertMergeTarget(currentBranch, target) {
|
|
|
37
37
|
async function inspect(projectPath, branch) {
|
|
38
38
|
const repo = await inspectRepo(projectPath);
|
|
39
39
|
if (!(await validBranchName(repo.projectPath, branch))) throw new TypeError('Invalid local branch name.');
|
|
40
|
-
|
|
40
|
+
// Both tips in one read when both exist; otherwise each on its own, as before.
|
|
41
|
+
const pair = repo.currentBranch ? await branchCommitPair(repo.projectPath, branch, repo.currentBranch) : null;
|
|
42
|
+
const commit = pair ? pair[0] : await branchCommit(repo.projectPath, branch);
|
|
41
43
|
if (!commit) throw worktreeError('WORKTREE_MISSING', 'The local branch no longer exists.');
|
|
42
44
|
const holder = (await listWorktrees(repo.projectPath)).find((row) => row.branch === branch && !row.prunable);
|
|
43
45
|
const entry = holder ? await getWorktreeByPath(holder.path) : null;
|
|
@@ -46,7 +48,11 @@ async function inspect(projectPath, branch) {
|
|
|
46
48
|
branch,
|
|
47
49
|
commit,
|
|
48
50
|
target: repo.currentBranch,
|
|
49
|
-
targetCommit:
|
|
51
|
+
targetCommit: pair
|
|
52
|
+
? pair[1]
|
|
53
|
+
: repo.currentBranch
|
|
54
|
+
? await branchCommit(repo.projectPath, repo.currentBranch)
|
|
55
|
+
: null,
|
|
50
56
|
path: holder?.path ?? null,
|
|
51
57
|
worktreeId: entry?.id ?? null,
|
|
52
58
|
kind: holder ? (samePath(holder.path, repo.topLevel) ? 'project' : entry ? 'managed' : 'external') : 'free',
|
|
@@ -27,6 +27,7 @@ const WORKTREE_ENTRY_STATES = Object.freeze(['creating', 'ready', 'deleting', 'r
|
|
|
27
27
|
export const WORKTREE_ERROR_CODES = Object.freeze({
|
|
28
28
|
WORKTREE_BUSY_JOBS: 'WORKTREE_BUSY_JOBS',
|
|
29
29
|
WORKTREE_BRANCH_OCCUPIED: 'WORKTREE_BRANCH_OCCUPIED',
|
|
30
|
+
WORKTREE_DEFAULT_BRANCH: 'WORKTREE_DEFAULT_BRANCH',
|
|
30
31
|
WORKTREE_DISK_INSUFFICIENT: 'WORKTREE_DISK_INSUFFICIENT',
|
|
31
32
|
WORKTREE_LARGE_REPO_CONFIRM: 'WORKTREE_LARGE_REPO_CONFIRM',
|
|
32
33
|
WORKTREE_DIRTY: 'WORKTREE_DIRTY',
|
|
@@ -54,6 +55,8 @@ export const WORKTREE_ERROR_CODES = Object.freeze({
|
|
|
54
55
|
const DEFAULT_ERROR_MESSAGES = Object.freeze({
|
|
55
56
|
WORKTREE_BUSY_JOBS: 'The session has running jobs; wait for them or stop them before changing its workspace.',
|
|
56
57
|
WORKTREE_BRANCH_OCCUPIED: 'The branch is checked out in another worktree.',
|
|
58
|
+
WORKTREE_DEFAULT_BRANCH:
|
|
59
|
+
"The repository's default branch can't be opened in a worktree: the project folder could not switch to it while the worktree exists. Check it out in the project folder, or create a new branch for the worktree.",
|
|
57
60
|
WORKTREE_DISK_INSUFFICIENT: 'Not enough free disk space to create the worktree.',
|
|
58
61
|
WORKTREE_LARGE_REPO_CONFIRM: 'The repository is large; confirm before creating a worktree.',
|
|
59
62
|
WORKTREE_DIRTY: 'The worktree has uncommitted changes.',
|
|
@@ -193,7 +196,7 @@ function record(value, name) {
|
|
|
193
196
|
* @property {string|null} worktreeId Managed worktree id; null when running in the project folder.
|
|
194
197
|
* @property {string|null} branch Checked-out branch at `cwd`; null when detached or not a repository.
|
|
195
198
|
* @property {string|null} baseCommit Commit the worktree branch was created from.
|
|
196
|
-
* @property {string|null} mergeTarget Branch the worktree merges into (the project folder's branch).
|
|
199
|
+
* @property {string|null} mergeTarget Branch the worktree merges into (the project folder's branch); null for an existing branch until a merge records it.
|
|
197
200
|
* @property {number} revision Monotonic; advances on every committed workspace change.
|
|
198
201
|
* @property {WorkspaceTransition} transition In-flight transition marker.
|
|
199
202
|
*/
|
|
@@ -132,6 +132,45 @@ async function assertProjectFolder(cwd) {
|
|
|
132
132
|
export async function inspectRepo(projectPath, { signal } = {}) {
|
|
133
133
|
const cwd = canonicalPath(projectPath);
|
|
134
134
|
await assertProjectFolder(cwd);
|
|
135
|
+
// The usual repository answers every fact in one `rev-parse`: bareness,
|
|
136
|
+
// top level, common dir and HEAD's commit, in that order, then the echoed
|
|
137
|
+
// `--` (which makes an unresolvable HEAD fail the call instead of being
|
|
138
|
+
// read as a path). Any other answer (bare, no work tree, no commits, an
|
|
139
|
+
// unexpected shape) takes the step-by-step reads, whose results and errors
|
|
140
|
+
// are the contract.
|
|
141
|
+
const facts = await runGit(
|
|
142
|
+
cwd,
|
|
143
|
+
[
|
|
144
|
+
'rev-parse',
|
|
145
|
+
'--is-bare-repository',
|
|
146
|
+
'--show-toplevel',
|
|
147
|
+
'--path-format=absolute',
|
|
148
|
+
'--git-common-dir',
|
|
149
|
+
'HEAD^{commit}',
|
|
150
|
+
'--',
|
|
151
|
+
],
|
|
152
|
+
{ signal, allowFailure: true }
|
|
153
|
+
);
|
|
154
|
+
const [factBare, factTop, factCommonDir, factHead, dashes, ...rest] = String(facts.stdout || '')
|
|
155
|
+
.split(/\r?\n/)
|
|
156
|
+
.map((line) => line.trim());
|
|
157
|
+
if (
|
|
158
|
+
facts.code === 0 &&
|
|
159
|
+
factBare === 'false' &&
|
|
160
|
+
factTop &&
|
|
161
|
+
factCommonDir &&
|
|
162
|
+
/^[0-9a-f]{40,64}$/.test(factHead) &&
|
|
163
|
+
dashes === '--' &&
|
|
164
|
+
rest.every((line) => line === '')
|
|
165
|
+
) {
|
|
166
|
+
return {
|
|
167
|
+
projectPath: cwd,
|
|
168
|
+
topLevel: canonicalPath(factTop),
|
|
169
|
+
repoCommonDir: canonicalPath(factCommonDir),
|
|
170
|
+
headCommit: factHead,
|
|
171
|
+
currentBranch: await currentBranch(cwd, { signal }),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
135
174
|
const probe = await runGit(cwd, ['rev-parse', '--is-bare-repository', '--path-format=absolute', '--git-common-dir'], {
|
|
136
175
|
signal,
|
|
137
176
|
allowFailure: true,
|
|
@@ -181,11 +220,44 @@ export async function branchCommit(cwd, branch, { signal } = {}) {
|
|
|
181
220
|
return result.code === 0 ? firstLine(result.stdout) : null;
|
|
182
221
|
}
|
|
183
222
|
|
|
223
|
+
/** Full commit ids of two existing local branches from one `rev-parse` (the
|
|
224
|
+
* trailing `--` makes a missing one fail the call), or null whenever that
|
|
225
|
+
* answer is not exactly two commit ids: the caller then reads each with
|
|
226
|
+
* branchCommit(). */
|
|
227
|
+
export async function branchCommitPair(cwd, first, second, { signal } = {}) {
|
|
228
|
+
const result = await runGit(cwd, ['rev-parse', `refs/heads/${first}^{commit}`, `refs/heads/${second}^{commit}`, '--'], {
|
|
229
|
+
signal,
|
|
230
|
+
allowFailure: true,
|
|
231
|
+
});
|
|
232
|
+
const [a, b, dashes, ...rest] = String(result.stdout || '')
|
|
233
|
+
.split(/\r?\n/)
|
|
234
|
+
.map((line) => line.trim());
|
|
235
|
+
const commit = /^[0-9a-f]{40,64}$/;
|
|
236
|
+
return result.code === 0 && commit.test(a) && commit.test(b) && dashes === '--' && rest.every((line) => !line)
|
|
237
|
+
? [a, b]
|
|
238
|
+
: null;
|
|
239
|
+
}
|
|
240
|
+
|
|
184
241
|
export async function localBranches(cwd, { signal, prefix = '' } = {}) {
|
|
185
242
|
const result = await runGit(cwd, ['for-each-ref', '--format=%(refname:short)', `refs/heads/${prefix}`], { signal });
|
|
186
243
|
return lines(result.stdout);
|
|
187
244
|
}
|
|
188
245
|
|
|
246
|
+
/** The local branch names the repository's remotes declare as their default (`refs/remotes/<remote>/HEAD`). */
|
|
247
|
+
export async function remoteDefaultBranches(cwd) {
|
|
248
|
+
const result = await runGit(cwd, ['for-each-ref', '--format=%(refname) %(symref)', 'refs/remotes'], {
|
|
249
|
+
readOnly: true,
|
|
250
|
+
});
|
|
251
|
+
const names = [];
|
|
252
|
+
for (const line of lines(result.stdout)) {
|
|
253
|
+
const [ref, target] = line.split(' ');
|
|
254
|
+
if (!ref.endsWith('/HEAD') || !target) continue;
|
|
255
|
+
const remote = ref.slice(0, -'HEAD'.length);
|
|
256
|
+
if (target.startsWith(remote)) names.push(target.slice(remote.length));
|
|
257
|
+
}
|
|
258
|
+
return names;
|
|
259
|
+
}
|
|
260
|
+
|
|
189
261
|
export async function validBranchName(cwd, branch) {
|
|
190
262
|
if (typeof branch !== 'string' || !branch.trim() || branch.startsWith('-')) return false;
|
|
191
263
|
const result = await runGit(cwd, ['check-ref-format', '--branch', branch], { allowFailure: true });
|