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
|
@@ -1,16 +1,52 @@
|
|
|
1
|
-
// Cross-process advisory lock for patch/write operations. Uses a
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
import { openSync, closeSync, writeSync, readFileSync, unlinkSync,
|
|
1
|
+
// Cross-process advisory lock for patch/write operations. Uses a lockfile
|
|
2
|
+
// written exclusively (`wx`) and a process.kill(pid, 0) liveness check to
|
|
3
|
+
// clean up after crashed holders. Pair with the in-process withPathLock for
|
|
4
|
+
// the same target: in-process serialises async callers in this Node, advisory
|
|
5
|
+
// lock serialises across Node processes.
|
|
6
|
+
import { openSync, closeSync, writeSync, readFileSync, unlinkSync, realpathSync } from 'node:fs';
|
|
7
7
|
import { compareCodePoints } from '../../../../shared/code-point-order.mjs';
|
|
8
|
+
import { ensurePrivateRuntimeRoot } from '../../../../shared/runtime-root.mjs';
|
|
8
9
|
import { sleep } from '../../../../shared/sleep.mjs';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { dirname, basename, join, resolve } from 'node:path';
|
|
12
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
// The deepest existing component (the target itself when present, so a leaf
|
|
15
|
+
// symlink shares its referent's lock like atomicWrite writes through it) is
|
|
16
|
+
// resolved; components not created yet are appended as given.
|
|
17
|
+
function canonicalLockTarget(targetPath) {
|
|
18
|
+
const resolved = resolve(targetPath);
|
|
19
|
+
const tail = [];
|
|
20
|
+
let dir = resolved;
|
|
21
|
+
for (;;) {
|
|
22
|
+
try {
|
|
23
|
+
return join(realpathSync.native(dir), ...tail.reverse());
|
|
24
|
+
} catch {
|
|
25
|
+
if (dirname(dir) === dir) return resolved;
|
|
26
|
+
tail.push(basename(dir));
|
|
27
|
+
dir = dirname(dir);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// External files can be shared by profiles, so neither MIXDOG_HOME/DATA_DIR
|
|
33
|
+
// nor MIXDOG_RUNTIME_ROOT may choose this namespace. Temp directories can
|
|
34
|
+
// differ between processes too. Keep locks in the OS user's local home,
|
|
35
|
+
// never beside the target: sync drives such as MYBOX refuse sibling dotfiles.
|
|
36
|
+
// The pid liveness check coordinates only processes on this host.
|
|
37
|
+
function lockDirectory() {
|
|
38
|
+
const root =
|
|
39
|
+
process.platform === 'win32'
|
|
40
|
+
? join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'mixdog')
|
|
41
|
+
: join(homedir(), '.mixdog');
|
|
42
|
+
return join(root, 'locks');
|
|
43
|
+
}
|
|
11
44
|
|
|
12
45
|
function lockFileFor(targetPath) {
|
|
13
|
-
|
|
46
|
+
const canonical = canonicalLockTarget(targetPath);
|
|
47
|
+
const key = process.platform === 'win32' ? canonical.toLowerCase() : canonical;
|
|
48
|
+
const hash = createHash('sha256').update(key).digest('hex');
|
|
49
|
+
return join(lockDirectory(), `${hash}.mixdog-lock`);
|
|
14
50
|
}
|
|
15
51
|
|
|
16
52
|
function isPidAlive(pid) {
|
|
@@ -87,15 +123,12 @@ function tryCreateLock(lockFile, token) {
|
|
|
87
123
|
|
|
88
124
|
async function acquireAdvisoryLock(targetPath, { timeoutMs = 5000, pollMs = 25 } = {}) {
|
|
89
125
|
const lockFile = lockFileFor(targetPath);
|
|
90
|
-
|
|
91
|
-
// next to the target, so writes to new files in missing parent dirs would
|
|
92
|
-
// otherwise fail with ENOENT here.
|
|
93
|
-
mkdirSync(dirname(lockFile), { recursive: true });
|
|
126
|
+
ensurePrivateRuntimeRoot(dirname(lockFile));
|
|
94
127
|
const token = `${process.pid}.${randomBytes(8).toString('hex')}`;
|
|
95
128
|
const deadline = Date.now() + timeoutMs;
|
|
96
129
|
let stalePostDeadlineRetryUsed = false;
|
|
97
130
|
const timeoutError = (holderPid) => {
|
|
98
|
-
const err = new Error(`advisory lock timeout: ${lockFile} held by pid ${holderPid}`);
|
|
131
|
+
const err = new Error(`advisory lock timeout: ${targetPath} (${lockFile}) held by pid ${holderPid}`);
|
|
99
132
|
err.code = 'EAGAIN';
|
|
100
133
|
return err;
|
|
101
134
|
};
|
|
@@ -24,9 +24,24 @@ function expectedTargetSnapshotChanged(currentStat, expected) {
|
|
|
24
24
|
return !sameFileVersion(currentStat, expected);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Files the agent writes into a user's project are durable by default: without
|
|
28
|
+
// the flush a crash can publish the renamed file with every byte NUL.
|
|
29
|
+
// MIXDOG_ATOMIC_FSYNC=0|false|no|off opts out.
|
|
27
30
|
function atomicWriteShouldFsync(value) {
|
|
28
31
|
if (value === true || value === false) return value;
|
|
29
|
-
return
|
|
32
|
+
return !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_ATOMIC_FSYNC || ''));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Filesystems that refuse fsync answer with one of these; durability is
|
|
36
|
+
// best-effort there and must not fail the write.
|
|
37
|
+
const FSYNC_TOLERATED = new Set(['EPERM', 'ENOTSUP', 'EINVAL']);
|
|
38
|
+
|
|
39
|
+
async function syncHandle(fh) {
|
|
40
|
+
try {
|
|
41
|
+
await fh.sync();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (!FSYNC_TOLERATED.has(error?.code)) throw error;
|
|
44
|
+
}
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
// 'wx' preflight creates an empty placeholder at targetPath; if the write is
|
|
@@ -104,7 +119,10 @@ async function writeTempFile(tmp, content, { mode, fsync }) {
|
|
|
104
119
|
if (payloadByteLength(content) > STREAMING_THRESHOLD_BYTES) {
|
|
105
120
|
// Streaming path: avoid buffering the entire payload through
|
|
106
121
|
// fh.writeFile (which copies into a single Buffer).
|
|
107
|
-
|
|
122
|
+
// Owner-writable while we flush: the fsync reopens the file for writing,
|
|
123
|
+
// which a read-only mode would refuse; the final mode is applied after.
|
|
124
|
+
const streamMode = fsync ? mode | 0o200 : mode;
|
|
125
|
+
const ws = createWriteStream(tmp, { flags: 'wx', mode: streamMode });
|
|
108
126
|
// Stream construction precedes open; only the open event proves ownership.
|
|
109
127
|
ws.once('open', () => {
|
|
110
128
|
ownsTemp = true;
|
|
@@ -115,15 +133,16 @@ async function writeTempFile(tmp, content, { mode, fsync }) {
|
|
|
115
133
|
await pipeline(source, ws);
|
|
116
134
|
if (fsync) {
|
|
117
135
|
fh = await fsPromises.open(tmp, 'r+');
|
|
118
|
-
await fh
|
|
136
|
+
await syncHandle(fh);
|
|
119
137
|
await fh.close();
|
|
120
138
|
fh = null;
|
|
139
|
+
if (streamMode !== mode) await fsPromises.chmod(tmp, mode);
|
|
121
140
|
}
|
|
122
141
|
} else {
|
|
123
142
|
fh = await fsPromises.open(tmp, 'wx', mode);
|
|
124
143
|
ownsTemp = true;
|
|
125
144
|
await fh.writeFile(content);
|
|
126
|
-
if (fsync) await fh
|
|
145
|
+
if (fsync) await syncHandle(fh);
|
|
127
146
|
await fh.close();
|
|
128
147
|
fh = null;
|
|
129
148
|
}
|
|
@@ -260,7 +279,8 @@ export async function atomicWrite(
|
|
|
260
279
|
// beside the real target, so the rename stays on one filesystem.
|
|
261
280
|
const writeTarget = symlinkWriteTarget(targetPath) ?? targetPath;
|
|
262
281
|
const dir = dirname(writeTarget);
|
|
263
|
-
|
|
282
|
+
// No leading dot: sync-drive folders (e.g. Naver MYBOX) refuse dot-prefixed names.
|
|
283
|
+
const tmp = join(dir, `${basename(writeTarget)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
|
|
264
284
|
const existingStat = statOrNull(writeTarget);
|
|
265
285
|
let effectiveMode = mode;
|
|
266
286
|
if (effectiveMode === undefined && existingStat) effectiveMode = existingStat.mode & 0o777;
|
|
@@ -3,7 +3,7 @@ import { killShellJob, watchBackgroundShellJob } from '../shell-jobs.mjs';
|
|
|
3
3
|
import { renderBackgroundPartialOutput } from '../shell-output.mjs';
|
|
4
4
|
import { readShellTaskOutput } from '../lib/shell-task-output.mjs';
|
|
5
5
|
import { normalizeOutputPath } from '../path-utils.mjs';
|
|
6
|
-
import { cleanupArtifactOnTaskSettled, consumeTeeArtifact } from './transport-artifacts.mjs';
|
|
6
|
+
import { cleanupArtifactAfter, cleanupArtifactOnTaskSettled, consumeTeeArtifact } from './transport-artifacts.mjs';
|
|
7
7
|
import { _backgroundResultLines, _prependDestructiveWarning } from './result-format.mjs';
|
|
8
8
|
|
|
9
9
|
const DEFAULT_BACKGROUND_MESSAGE =
|
|
@@ -19,6 +19,7 @@ export function shellTaskIdentity(command, options) {
|
|
|
19
19
|
callerSessionId: options?.callerSessionId || options?.sessionId || null,
|
|
20
20
|
routingSessionId: options?.routingSessionId || options?.sessionId || null,
|
|
21
21
|
clientHostPid: options?.clientHostPid,
|
|
22
|
+
hop: options?.hop,
|
|
22
23
|
},
|
|
23
24
|
};
|
|
24
25
|
}
|
|
@@ -61,6 +62,7 @@ export function renderBackgroundedResult({ result, command, cwd, options, starte
|
|
|
61
62
|
callerSessionId: options?.callerSessionId || options?.sessionId,
|
|
62
63
|
routingSessionId: options?.routingSessionId || options?.sessionId,
|
|
63
64
|
clientHostPid: options?.clientHostPid,
|
|
65
|
+
hop: options?.hop,
|
|
64
66
|
});
|
|
65
67
|
} catch {
|
|
66
68
|
/* best effort */
|
|
@@ -68,8 +70,9 @@ export function renderBackgroundedResult({ result, command, cwd, options, starte
|
|
|
68
70
|
}
|
|
69
71
|
// The promoted producer is still writing into the tee file, so it cannot be
|
|
70
72
|
// consumed here — but it must not survive the command either. Consume (and
|
|
71
|
-
// delete) it when the task settles.
|
|
72
|
-
if (teePlan) cleanupArtifactOnTaskSettled(teePlan.teePath, result.jobId, consumeTeeArtifact);
|
|
73
|
+
// delete) it when the task settles — for a queued command, its run.
|
|
74
|
+
if (teePlan && result.jobId) cleanupArtifactOnTaskSettled(teePlan.teePath, result.jobId, consumeTeeArtifact);
|
|
75
|
+
else if (teePlan) cleanupArtifactAfter(teePlan.teePath, result.settled, consumeTeeArtifact);
|
|
73
76
|
let taskBlock = null;
|
|
74
77
|
if (task) taskBlock = renderBackgroundTask(task, { ownerTool: true });
|
|
75
78
|
else if (result.jobId) taskBlock = `[task_id: ${result.jobId}]`;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { renderBackgroundTask, startBackgroundTask } from '../../../../../shared/background-tasks.mjs';
|
|
2
2
|
import { killShellDescendants, waitForShellDescendants } from '../../lib/shell-descendants.mjs';
|
|
3
3
|
import { releaseShellJobWorkspace } from '../lib/shell-job-records.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { readFilterTeeCapture } from '../shell-analysis.mjs';
|
|
5
5
|
import { compactShellOutputLosslessly, renderShellOutputBody } from '../shell-lossless-compact.mjs';
|
|
6
|
-
import { removeTransportFileNow } from './transport-artifacts.mjs';
|
|
6
|
+
import { cleanupArtifactAfter, removeTransportFileNow } from './transport-artifacts.mjs';
|
|
7
7
|
import { DELIVERY_FULL_CHARS, EMPTY_PIECE, fitPieces, shellTextPiece } from '../lib/shell-delivery.mjs';
|
|
8
8
|
import { shellLogPiece } from '../lib/shell-task-output.mjs';
|
|
9
9
|
import { shellTaskIdentity } from './backgrounded-result.mjs';
|
|
@@ -74,17 +74,16 @@ function survivingDescendantsWarning(result, taskContext) {
|
|
|
74
74
|
return [heading, renderBackgroundTask(task, { ownerTool: true })].join('\n');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// Filter-swallow rescue: the tee file is ALWAYS consumed (deleted)
|
|
78
|
-
//
|
|
79
|
-
// the
|
|
80
|
-
// diagnostic turns.
|
|
81
|
-
function filterRescueNote(teePlan, { failed, stdout, stderr }) {
|
|
82
|
-
const rescueTail =
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
removeTransportFileNow(teePlan.teePath);
|
|
77
|
+
// Filter-swallow rescue: the tee file is ALWAYS consumed (deleted) — once the
|
|
78
|
+
// run's tree has settled when its result says it may still run; its tail is
|
|
79
|
+
// attached only when the run failed with an empty visible capture — the exact
|
|
80
|
+
// `(no output)` shape that previously cost the model extra diagnostic turns.
|
|
81
|
+
function filterRescueNote(teePlan, { failed, stdout, stderr }, settled) {
|
|
82
|
+
const rescueTail = readFilterTeeCapture(teePlan.teePath);
|
|
83
|
+
// Removal goes through the retry + exit-hook path: on Windows a process
|
|
84
|
+
// that survived an unconfirmed kill still holds the file.
|
|
85
|
+
if (settled) cleanupArtifactAfter(teePlan.teePath, settled);
|
|
86
|
+
else removeTransportFileNow(teePlan.teePath);
|
|
88
87
|
if (!failed || !rescueTail || stdout.trim() || stderr.trim()) return '';
|
|
89
88
|
return `\n\n[filter-swallowed output rescue] the command failed but its trailing filter(s) matched nothing, so the visible output was empty. Unfiltered pipeline output (tail):\n${rescueTail}`;
|
|
90
89
|
}
|
|
@@ -148,7 +147,7 @@ export function renderCompletedResult({
|
|
|
148
147
|
const { signal, exitCode, completedExit, benignExit, isReallyErrored } = outcome;
|
|
149
148
|
const commandExitedNonzero = completedExit && exitCode !== 0 && !benignExit;
|
|
150
149
|
const rescueNote = teePlan
|
|
151
|
-
? filterRescueNote(teePlan, { failed: isReallyErrored || commandExitedNonzero, stdout, stderr })
|
|
150
|
+
? filterRescueNote(teePlan, { failed: isReallyErrored || commandExitedNonzero, stdout, stderr }, result.settled)
|
|
152
151
|
: '';
|
|
153
152
|
const descendantWarning = survivingDescendantsWarning(result, { command, cwd, options, startedAtMs });
|
|
154
153
|
// rewriteNote states that the text which RAN is not the text the caller
|
|
@@ -79,6 +79,15 @@ export function removeTransportFileNow(file) {
|
|
|
79
79
|
finishTransportArtifact(file, removeTransportFile);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Remove `file` once a run's own lifetime promise settles: a queued command
|
|
83
|
+
* has no job to subscribe to until it runs. */
|
|
84
|
+
export function cleanupArtifactAfter(file, settled, remove = removeTransportFile) {
|
|
85
|
+
if (!file) return;
|
|
86
|
+
registerTransportArtifact(file);
|
|
87
|
+
const done = () => finishTransportArtifact(file, remove);
|
|
88
|
+
Promise.resolve(settled).then(done, done);
|
|
89
|
+
}
|
|
90
|
+
|
|
82
91
|
export function cleanupArtifactOnTaskSettled(file, jobId, remove = removeTransportFile) {
|
|
83
92
|
if (!file) return;
|
|
84
93
|
registerTransportArtifact(file);
|
|
@@ -12,7 +12,11 @@ import { recordShellCaptureTelemetry } from './shell-output.mjs';
|
|
|
12
12
|
import { normalizeErrorMessage } from './path-diagnostics.mjs';
|
|
13
13
|
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
14
14
|
import { formatShellToolFailure } from './bash-tool/result-format.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
cleanupArtifactAfter,
|
|
17
|
+
cleanupArtifactOnTaskSettled,
|
|
18
|
+
removeTransportFileNow,
|
|
19
|
+
} from './bash-tool/transport-artifacts.mjs';
|
|
16
20
|
import { combineAbortSignals, sessionAbortSignal } from './bash-tool/abort-signals.mjs';
|
|
17
21
|
import { beginInterruptibleTaskWait } from '../../session/task-wait-control.mjs';
|
|
18
22
|
import { prepareShellCommand } from './bash-tool/command-preflight.mjs';
|
|
@@ -39,9 +43,10 @@ export { buildShellSpawnEnv } from './bash-tool/spawn-env.mjs';
|
|
|
39
43
|
export { executeTaskTool } from './task-tool.mjs';
|
|
40
44
|
|
|
41
45
|
// Reads cached after a mutating command must not serve pre-command state. A
|
|
42
|
-
// promoted command has barely started when the tool returns
|
|
43
|
-
//
|
|
44
|
-
|
|
46
|
+
// promoted command has barely started when the tool returns, a queued one has
|
|
47
|
+
// not started at all and a killed tree may still run, so the invalidation
|
|
48
|
+
// repeats when it settles.
|
|
49
|
+
function settleMutationCaches(shellEffects, backgroundJobId, runSettled = null) {
|
|
45
50
|
const invalidate = () => {
|
|
46
51
|
if (shellEffects.mutationMode === 'paths') {
|
|
47
52
|
invalidateBuiltinResultCache(shellEffects.paths);
|
|
@@ -55,7 +60,7 @@ function settleMutationCaches(shellEffects, backgroundJobId, queuedSettled = nul
|
|
|
55
60
|
if (backgroundJobId && shellEffects.mutationMode !== 'none') {
|
|
56
61
|
subscribeShellJobSettled(backgroundJobId, invalidate);
|
|
57
62
|
}
|
|
58
|
-
if (
|
|
63
|
+
if (runSettled && shellEffects.mutationMode !== 'none') runSettled.then(invalidate).catch(() => {});
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export async function executeBashTool(args, workDir, options = {}) {
|
|
@@ -89,7 +94,8 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
89
94
|
const inputWake = backgroundOnTimeout ? beginInterruptibleTaskWait(options?.sessionId) : null;
|
|
90
95
|
let backgrounded = false;
|
|
91
96
|
let backgroundJobId = null;
|
|
92
|
-
|
|
97
|
+
// A queued run, or a foreground tree that may outlive its result, settles later.
|
|
98
|
+
let runSettled = null;
|
|
93
99
|
try {
|
|
94
100
|
const invocation = await planShellInvocation({ command, resolvedSpec, cwd: bashWorkDir });
|
|
95
101
|
if (invocation.failure) return invocation.failure;
|
|
@@ -127,7 +133,7 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
127
133
|
});
|
|
128
134
|
backgrounded = result.backgrounded === true;
|
|
129
135
|
backgroundJobId = backgrounded ? result.jobId || null : null;
|
|
130
|
-
|
|
136
|
+
runSettled = result.settled || null;
|
|
131
137
|
const stdout = stripAnsi(result.stdout || '');
|
|
132
138
|
const stderr = stripAnsi(result.stderr || '');
|
|
133
139
|
recordShellCaptureTelemetry(options?.resultTelemetry, result, stdout, stderr);
|
|
@@ -152,14 +158,13 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
152
158
|
inputWake?.dispose();
|
|
153
159
|
combinedAbort?.cleanup?.();
|
|
154
160
|
if (hoisted.hoistPath) {
|
|
155
|
-
// A promoted or queued command still runs from the hoisted file
|
|
156
|
-
//
|
|
157
|
-
// locked — retried).
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
} else if (backgrounded) cleanupArtifactOnTaskSettled(hoisted.hoistPath, backgroundJobId);
|
|
161
|
+
// A promoted or queued command still runs from the hoisted file, and so
|
|
162
|
+
// may a killed tree until it settles; a finished foreground one is done
|
|
163
|
+
// with it now (an unconfirmed kill may keep it locked — retried).
|
|
164
|
+
if (runSettled) cleanupArtifactAfter(hoisted.hoistPath, runSettled);
|
|
165
|
+
else if (backgrounded) cleanupArtifactOnTaskSettled(hoisted.hoistPath, backgroundJobId);
|
|
161
166
|
else removeTransportFileNow(hoisted.hoistPath);
|
|
162
167
|
}
|
|
163
|
-
settleMutationCaches(shellEffects, backgroundJobId,
|
|
168
|
+
settleMutationCaches(shellEffects, backgroundJobId, runSettled);
|
|
164
169
|
}
|
|
165
170
|
}
|
|
@@ -87,7 +87,13 @@ export const BUILTIN_TOOLS = [
|
|
|
87
87
|
type: 'integer',
|
|
88
88
|
minimum: 1,
|
|
89
89
|
maximum: PUBLIC_READ_WINDOW_MAX,
|
|
90
|
-
description:
|
|
90
|
+
description:
|
|
91
|
+
'Maximum line count; default 500 per exact file, 25 per glob-expanded file; batch default. Lines only: PDFs use pages.',
|
|
92
|
+
},
|
|
93
|
+
pages: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
description:
|
|
96
|
+
'PDF page range "N" or "N-M", 1-based, ≤20 pages per call. Text pages return text; scanned pages (no text layer) return page images, or OCR text on a text-only model. Without pages, a PDF over 10 pages returns its first 5 pages and page count.',
|
|
91
97
|
},
|
|
92
98
|
},
|
|
93
99
|
required: ['file_path'],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isUtf8 } from 'node:buffer';
|
|
1
2
|
import { existsSync } from 'node:fs';
|
|
2
3
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { tmpdir } from 'node:os';
|
|
@@ -8,6 +9,10 @@ import { withBuiltinPathLocks } from './path-locks.mjs';
|
|
|
8
9
|
import { withAdvisoryLocks } from './advisory-lock.mjs';
|
|
9
10
|
import { gitRepoLockKey, withGitWorktreeWriteLock } from './git-repo-rw-lock.mjs';
|
|
10
11
|
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
12
|
+
import { tryAdmitWorkspaceActivity } from '../../../../shared/worktrees/activity-gate.mjs';
|
|
13
|
+
import { WORKTREE_SNAPSHOT_KIND } from '../../../../shared/worktrees/contract.mjs';
|
|
14
|
+
import { isWithin } from '../../../../shared/worktrees/paths.mjs';
|
|
15
|
+
import { readRegistrySync } from '../../../../shared/worktrees/registry.mjs';
|
|
11
16
|
import { runAbortable } from '../../../../shared/abort-race.mjs';
|
|
12
17
|
import { sleep } from '../../../../shared/sleep.mjs';
|
|
13
18
|
import { capMarker } from '../../../../shared/tool-output-marker.mjs';
|
|
@@ -26,6 +31,12 @@ import {
|
|
|
26
31
|
diffSnapshotMatches,
|
|
27
32
|
getDiffSnapshot,
|
|
28
33
|
} from './git-partial-stage.mjs';
|
|
34
|
+
import {
|
|
35
|
+
executeRawWorktree,
|
|
36
|
+
executeWorktreeAction,
|
|
37
|
+
WORKTREE_INPUT_FIELDS,
|
|
38
|
+
WORKTREE_TOOL_ACTIONS,
|
|
39
|
+
} from './git-worktree-tool.mjs';
|
|
29
40
|
|
|
30
41
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
31
42
|
const MAX_CAPTURE_BYTES = 128 * 1024 * 1024;
|
|
@@ -84,14 +95,15 @@ export const GIT_TOOL_DEF = {
|
|
|
84
95
|
compressible: true,
|
|
85
96
|
},
|
|
86
97
|
description:
|
|
87
|
-
'Run Git here, never through shell. An array (max 10) runs in order and stops on failure; batch read-only commands in one. diff for known changes, status to discover them; history only when needed. git diff or git diff -- <paths> includes untracked files. Set include_stage_ids:true to select changes, then action:stage with diff_id/change_ids. Staging rejects stale or cross-Project diffs and returns per-file staged counts.',
|
|
98
|
+
'Run Git here, never through shell. An array (max 10) runs in order and stops on failure; batch read-only commands in one. diff for known changes, status to discover them; history only when needed. git diff or git diff -- <paths> includes untracked files. Set include_stage_ids:true to select changes, then action:stage with diff_id/change_ids. Staging rejects stale or cross-Project diffs and returns per-file staged counts. Worktrees are managed only: action:worktree (Lead only) moves a session into/out of its managed worktree, merges, removes, lists, prunes; git worktree add/remove/list/prune map to managed operations (add -b keeps the session in place; add --detach makes a read-only snapshot worktree, removed when its turn ends; list --porcelain stays raw git; move/lock/unlock/repair are refused). Merge or remove only when the user asks; carry and large-size answers come from the user; force only on explicit request.',
|
|
88
99
|
inputSchema: {
|
|
89
100
|
type: 'object',
|
|
90
101
|
properties: {
|
|
91
102
|
action: {
|
|
92
103
|
type: 'string',
|
|
93
|
-
enum: ['command', 'stage'],
|
|
94
|
-
description:
|
|
104
|
+
enum: ['command', 'stage', 'worktree'],
|
|
105
|
+
description:
|
|
106
|
+
'Default command. stage uses diff_id/change_ids instead of command. worktree uses worktree_action and its fields.',
|
|
95
107
|
},
|
|
96
108
|
command: {
|
|
97
109
|
anyOf: [
|
|
@@ -118,6 +130,29 @@ export const GIT_TOOL_DEF = {
|
|
|
118
130
|
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 50 }],
|
|
119
131
|
description: 'stage: exact change ID or IDs to stage, including new files.',
|
|
120
132
|
},
|
|
133
|
+
worktree_action: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
enum: [...WORKTREE_TOOL_ACTIONS],
|
|
136
|
+
description:
|
|
137
|
+
'enter: create or re-enter the managed worktree (branch: that branch, create: new); return: back to the project folder, worktree kept; merge: into its merge target; remove; list; prune.',
|
|
138
|
+
},
|
|
139
|
+
session_id: {
|
|
140
|
+
type: 'string',
|
|
141
|
+
description: 'enter/return/merge/remove: another session (it must be idle); default the caller.',
|
|
142
|
+
},
|
|
143
|
+
name: { type: 'string', description: 'enter: the new worktree name (branch <prefix><name>).' },
|
|
144
|
+
branch: { type: 'string', description: 'enter: use the worktree of this branch instead.' },
|
|
145
|
+
create: { type: 'boolean', description: 'enter with branch: create that branch.' },
|
|
146
|
+
carry: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
enum: ['leave', 'carry'],
|
|
149
|
+
description: 'enter: the user answer to WORKTREE_DIRTY_CHOICE (uncommitted project-folder changes).',
|
|
150
|
+
},
|
|
151
|
+
confirm_large: {
|
|
152
|
+
type: 'boolean',
|
|
153
|
+
description: 'enter or git worktree add: the user accepted the WORKTREE_LARGE_REPO_CONFIRM size.',
|
|
154
|
+
},
|
|
155
|
+
force: { type: 'boolean', description: 'remove: discard dirty/unmerged work; only on explicit user request.' },
|
|
121
156
|
},
|
|
122
157
|
additionalProperties: false,
|
|
123
158
|
},
|
|
@@ -136,6 +171,9 @@ function ok(data = {}) {
|
|
|
136
171
|
});
|
|
137
172
|
}
|
|
138
173
|
|
|
174
|
+
const NOT_UTF8_STAGE_NOTE =
|
|
175
|
+
'staging ids unavailable: this diff has bytes that are not UTF-8 (a file in another encoding), and staging its text would replace them; stage those files whole with git add -- <paths>';
|
|
176
|
+
|
|
139
177
|
function fail(message) {
|
|
140
178
|
return `error: ${message}`;
|
|
141
179
|
}
|
|
@@ -398,6 +436,9 @@ async function runProcess(program, argv, { cwd, signal, maxBytes = MAX_CAPTURE_B
|
|
|
398
436
|
...(optionalLocks ? {} : { GIT_OPTIONAL_LOCKS: '0' }),
|
|
399
437
|
},
|
|
400
438
|
outputLimit: maxBytes,
|
|
439
|
+
// Bytes as git wrote them, decoded once by the caller: a multibyte
|
|
440
|
+
// character split across two pipe reads must not become U+FFFD.
|
|
441
|
+
rawOutput: true,
|
|
401
442
|
},
|
|
402
443
|
});
|
|
403
444
|
if (!native?.child)
|
|
@@ -561,9 +602,13 @@ function prepare(plan) {
|
|
|
561
602
|
return { argv: [operation, ...args] };
|
|
562
603
|
}
|
|
563
604
|
|
|
605
|
+
// A stage applies the text of this diff, so a patch with bytes that are not
|
|
606
|
+
// UTF-8 (a legacy-encoded file) would be applied with U+FFFD in their place.
|
|
607
|
+
// `notUtf8` marks such a diff: it is shown, but never staged by id.
|
|
564
608
|
async function runStageableDiff(plan, argv, repo, signal) {
|
|
565
609
|
const result = await runGit(plan, argv, { signal, readOnly: true });
|
|
566
610
|
if (!succeeded(result)) return result;
|
|
611
|
+
result.notUtf8 = !isUtf8(result.stdout);
|
|
567
612
|
result.stdout = String(result.stdout);
|
|
568
613
|
result.stderr = String(result.stderr);
|
|
569
614
|
const paths = plan.args.slice(1);
|
|
@@ -593,6 +638,7 @@ async function runStageableDiff(plan, argv, repo, signal) {
|
|
|
593
638
|
);
|
|
594
639
|
// --no-index uses exit 1 for a successfully produced patch.
|
|
595
640
|
if (!succeeded({ ...added, exitCode: added.exitCode === 1 ? 0 : added.exitCode })) return added;
|
|
641
|
+
if (!isUtf8(added.stdout)) result.notUtf8 = true;
|
|
596
642
|
result.stdout = appendText(result.stdout, String(added.stdout));
|
|
597
643
|
result.stderr = appendText(result.stderr, String(added.stderr));
|
|
598
644
|
}
|
|
@@ -763,26 +809,14 @@ function creationTarget(plan) {
|
|
|
763
809
|
// --prune=now as unsafe beside a concurrent writer), so these hold the whole
|
|
764
810
|
// repository.
|
|
765
811
|
const REPO_WIDE_OPERATIONS = new Set(['gc', 'maintenance', 'prune', 'prune-packed', 'repack']);
|
|
766
|
-
const WORKTREE_VALUE_FLAGS = new Set(['-b', '-B', '--reason']);
|
|
767
812
|
|
|
768
813
|
// The worktrees a mutating plan touches; null holds the whole repository. A
|
|
769
|
-
// write changes its cwd's worktree,
|
|
770
|
-
//
|
|
771
|
-
//
|
|
814
|
+
// write changes its cwd's worktree, so writes on different worktrees never
|
|
815
|
+
// wait for each other. (`git worktree` never runs as a git write here: it
|
|
816
|
+
// maps to managed operations, which take the repository lock themselves.)
|
|
772
817
|
function writeScopes(plan, root) {
|
|
773
818
|
if (REPO_WIDE_OPERATIONS.has(plan.operation)) return null;
|
|
774
|
-
|
|
775
|
-
const [action, ...paths] = optionFreePositionals(plan.args, WORKTREE_VALUE_FLAGS);
|
|
776
|
-
const target = (path) => resolve(plan.cwd, path);
|
|
777
|
-
// git also names a worktree by a unique path suffix, which no cwd-relative
|
|
778
|
-
// path resolves; an argument that is not a worktree here holds the whole
|
|
779
|
-
// repository instead of guessing (so do prune and an argument-less repair).
|
|
780
|
-
const existing = (path) => (existsSync(join(target(path), '.git')) ? target(path) : null);
|
|
781
|
-
let scopes = null;
|
|
782
|
-
if (action === 'add') scopes = paths.slice(0, 1).map(target);
|
|
783
|
-
else if (action === 'move' && paths.length === 2) scopes = [existing(paths[0]), target(paths[1])];
|
|
784
|
-
else if (['remove', 'lock', 'unlock', 'repair'].includes(action)) scopes = paths.map(existing);
|
|
785
|
-
return scopes?.length && scopes.every(Boolean) ? scopes : null;
|
|
819
|
+
return [root];
|
|
786
820
|
}
|
|
787
821
|
|
|
788
822
|
// A mutating git call holds the in-process and cross-process locks on its
|
|
@@ -804,6 +838,34 @@ function withGitWriteLocks(lockKey, scopes, root, signal, callback) {
|
|
|
804
838
|
);
|
|
805
839
|
}
|
|
806
840
|
|
|
841
|
+
// The managed snapshot worktree `repo` lies in, else null. A registry that
|
|
842
|
+
// cannot be read names none: commands elsewhere run as before.
|
|
843
|
+
function snapshotContaining(repo) {
|
|
844
|
+
let entries;
|
|
845
|
+
try {
|
|
846
|
+
entries = readRegistrySync().entries;
|
|
847
|
+
} catch {
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
return (
|
|
851
|
+
Object.values(entries).find(
|
|
852
|
+
(record) => record?.kind === WORKTREE_SNAPSHOT_KIND && typeof record.path === 'string' && isWithin(record.path, repo)
|
|
853
|
+
) ?? null
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// A command run in a snapshot worktree is one of its consumers: the workspace
|
|
858
|
+
// lease it holds (activity-gate, the same as a background shell's) keeps the
|
|
859
|
+
// snapshot from being removed in any process until it ends, and a snapshot
|
|
860
|
+
// whose removal is under way refuses it. Returns the release.
|
|
861
|
+
function admitSnapshotCommand(repo, sessionId) {
|
|
862
|
+
const snapshot = snapshotContaining(repo);
|
|
863
|
+
if (!snapshot) return () => {};
|
|
864
|
+
const leave = tryAdmitWorkspaceActivity(sessionId || 'git-tool', repo, { kind: 'command' });
|
|
865
|
+
if (!leave) throw new Error(`the snapshot worktree ${snapshot.path} is being removed; nothing ran`);
|
|
866
|
+
return leave;
|
|
867
|
+
}
|
|
868
|
+
|
|
807
869
|
function invalidateAfterGitMutation() {
|
|
808
870
|
invalidateBuiltinResultCache();
|
|
809
871
|
drainCodeGraphCache();
|
|
@@ -873,6 +935,7 @@ async function executeGitStage(input, workDir, options = {}) {
|
|
|
873
935
|
};
|
|
874
936
|
const current = await runStageableDiff(snapshot.plan, snapshot.argv, repo, signal);
|
|
875
937
|
if (!succeeded(current)) return noted(commandFailure(snapshot.plan, current, limit));
|
|
938
|
+
if (current.notUtf8) return noted(fail(NOT_UTF8_STAGE_NOTE));
|
|
876
939
|
const raw = current.stdout;
|
|
877
940
|
if (!diffSnapshotMatches(snapshot, raw)) {
|
|
878
941
|
return answer({
|
|
@@ -919,6 +982,15 @@ async function executeSingleGitTool(input, workDir, options = {}) {
|
|
|
919
982
|
} catch (error) {
|
|
920
983
|
return rejected(error.message);
|
|
921
984
|
}
|
|
985
|
+
// No unmanaged worktree is ever made here: worktree commands run as their
|
|
986
|
+
// managed operations (git-worktree-tool.mjs); only `list --porcelain` stays git.
|
|
987
|
+
if (plan.operation === 'worktree') {
|
|
988
|
+
const mapped = await executeRawWorktree(plan, input, options);
|
|
989
|
+
if (mapped) {
|
|
990
|
+
if (!isReadOnly(plan)) invalidateAfterGitMutation();
|
|
991
|
+
return mapped;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
922
994
|
if (
|
|
923
995
|
plan.operation === 'archive' &&
|
|
924
996
|
!plan.args.some((value) => value === '-o' || value === '--output' || value.startsWith('--output='))
|
|
@@ -954,6 +1026,23 @@ async function executeSingleGitTool(input, workDir, options = {}) {
|
|
|
954
1026
|
const missing = probe ?? (await runGit(plan, ['rev-parse', '--show-toplevel'], { signal }));
|
|
955
1027
|
return commandResult(plan, missing, limit);
|
|
956
1028
|
}
|
|
1029
|
+
// A command in a snapshot worktree uses it like a worker would: it holds a
|
|
1030
|
+
// workspace lease there while it runs, so the snapshot is never removed
|
|
1031
|
+
// under it, and a snapshot being removed refuses it.
|
|
1032
|
+
let leaveSnapshot;
|
|
1033
|
+
try {
|
|
1034
|
+
leaveSnapshot = admitSnapshotCommand(repo, options.sessionId);
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
return rejected(error.message);
|
|
1037
|
+
}
|
|
1038
|
+
try {
|
|
1039
|
+
return await runInRepository(input, plan, { repo, lockKey, limit, signal, workDir });
|
|
1040
|
+
} finally {
|
|
1041
|
+
leaveSnapshot();
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
async function runInRepository(input, plan, { repo, lockKey, limit, signal, workDir }) {
|
|
957
1046
|
const prepared = prepare(plan);
|
|
958
1047
|
const stageableRequest = plan.operation === 'diff' && (plan.args.length === 0 || plan.args[0] === '--');
|
|
959
1048
|
if (stageableRequest) prepared.argv.splice(1, 0, '--no-textconv', '--src-prefix=a/', '--dst-prefix=b/');
|
|
@@ -965,6 +1054,11 @@ async function executeSingleGitTool(input, workDir, options = {}) {
|
|
|
965
1054
|
: await runGit(plan, prepared.argv, { signal, readOnly: true });
|
|
966
1055
|
if (!succeeded(result)) return commandResult(plan, result, limit);
|
|
967
1056
|
if (stageableRequest && input.include_stage_ids === true) {
|
|
1057
|
+
if (result.notUtf8) {
|
|
1058
|
+
const rendered = commandResult(plan, result, limit);
|
|
1059
|
+
rendered.text = appendText(rendered.text, NOT_UTF8_STAGE_NOTE);
|
|
1060
|
+
return rendered;
|
|
1061
|
+
}
|
|
968
1062
|
const snapshot = createDiffSnapshot({
|
|
969
1063
|
repo,
|
|
970
1064
|
scope: resolve(workDir || process.cwd()),
|
|
@@ -977,6 +1071,9 @@ async function executeSingleGitTool(input, workDir, options = {}) {
|
|
|
977
1071
|
return commandResult(plan, result, limit);
|
|
978
1072
|
}
|
|
979
1073
|
return withGitWriteLocks(lockKey, writeScopes(plan, repo), repo, signal, async (waitedMs) => {
|
|
1074
|
+
// A managed removal holds the same folder lock while it deletes: once it
|
|
1075
|
+
// is ours, a folder that is gone was removed, and nothing runs there.
|
|
1076
|
+
if (!existsSync(repo)) return rejected(`the worktree folder ${repo} was removed meanwhile; nothing ran`);
|
|
980
1077
|
const result = await runGitWrite(plan, prepared.argv, { signal });
|
|
981
1078
|
invalidateAfterGitMutation();
|
|
982
1079
|
const rendered = commandResult(plan, result, limit);
|
|
@@ -1064,13 +1161,23 @@ export async function executeGitTool(rawInput, workDir, options = {}) {
|
|
|
1064
1161
|
if (rawInput.change_ids !== undefined) input.change_ids = parseStringifiedArray(rawInput.change_ids);
|
|
1065
1162
|
if (rawInput.command === undefined) delete input.command;
|
|
1066
1163
|
const action = input.action ?? 'command';
|
|
1164
|
+
if (action === 'worktree') {
|
|
1165
|
+
const text = await executeWorktreeAction(input, workDir, options);
|
|
1166
|
+
if (input.worktree_action !== 'list') invalidateAfterGitMutation();
|
|
1167
|
+
return text;
|
|
1168
|
+
}
|
|
1169
|
+
const worktreeField = WORKTREE_INPUT_FIELDS.find((field) => input[field] !== undefined);
|
|
1170
|
+
if (worktreeField) return fail(`git ${worktreeField} requires action:worktree`);
|
|
1171
|
+
if (input.confirm_large !== undefined && action !== 'command') {
|
|
1172
|
+
return fail('git confirm_large applies to action:worktree or a git worktree add command');
|
|
1173
|
+
}
|
|
1067
1174
|
if (action === 'stage') {
|
|
1068
1175
|
if (input.command !== undefined || input.include_stage_ids !== undefined) {
|
|
1069
1176
|
return fail('git action:stage accepts diff_id/change_ids, not command/include_stage_ids');
|
|
1070
1177
|
}
|
|
1071
1178
|
return executeGitStage(input, workDir, options);
|
|
1072
1179
|
}
|
|
1073
|
-
if (action !== 'command') return fail('git action must be command or
|
|
1180
|
+
if (action !== 'command') return fail('git action must be command, stage or worktree');
|
|
1074
1181
|
if (input.diff_id !== undefined || input.change_ids !== undefined) {
|
|
1075
1182
|
return fail('git diff_id/change_ids require action:stage');
|
|
1076
1183
|
}
|