mixdog 1.1.2 → 1.2.1
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 -5
- package/package.json +2 -1
- package/scripts/lib/run-node-tests.mjs +9 -0
- package/scripts/prune-desktop-runtime.mjs +3 -0
- package/scripts/test-direct.mjs +4 -2
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/pptx/SKILL.md +6 -6
- package/src/defaults/skills/pptx/references/review.md +6 -4
- package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
- package/src/defaults/skills/setup/references/actions.md +18 -5
- package/src/defaults/skills/visualize/SKILL.md +160 -0
- package/src/defaults/skills/visualize/references/craft.md +88 -0
- package/src/headless-command.mjs +56 -3
- package/src/headless-exec.mjs +17 -1
- package/src/help.mjs +2 -0
- package/src/rules/shared/60-verification.md +3 -1
- package/src/rules/shared/70-delivery.md +4 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
- package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +19 -5
- package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
- package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
- package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
- package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
- package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
- package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
- package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
- package/src/runtime/attachments/store.d.mts +3 -0
- package/src/runtime/attachments/store.mjs +38 -0
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
- package/src/runtime/channels/lib/worker-main.mjs +17 -14
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
- package/src/runtime/effort-judge/judge-client.mjs +56 -18
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/session-ingest.mjs +2 -2
- package/src/runtime/shared/atomic-file.mjs +33 -6
- package/src/runtime/shared/best-effort-stderr.mjs +8 -0
- package/src/runtime/shared/code-graph-state.mjs +12 -0
- package/src/runtime/shared/defaults-separation.mjs +2 -0
- package/src/runtime/shared/extension-scopes.mjs +5 -3
- package/src/runtime/shared/file-lock.mjs +37 -11
- package/src/runtime/shared/git-runner-worker.mjs +3 -2
- package/src/runtime/shared/git-runner.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
- package/src/runtime/shared/llm/default-context-window.mjs +6 -0
- package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
- package/src/runtime/shared/llm/model-catalog.mjs +240 -130
- package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
- package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
- package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
- package/src/runtime/shared/model-proxy-mode.mjs +19 -0
- package/src/runtime/shared/plugin-manifest.mjs +18 -9
- package/src/runtime/shared/pristine-execution-contract.json +9 -2
- package/src/runtime/shared/pristine-execution.mjs +28 -4
- package/src/runtime/shared/provider-accounts.mjs +125 -21
- package/src/runtime/shared/provider-api-key.mjs +33 -1
- package/src/runtime/shared/schedule-model-ref.mjs +11 -2
- package/src/runtime/shared/session-usage-totals.mjs +19 -0
- package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
- package/src/runtime/shared/shell-display.mjs +9 -3
- package/src/runtime/shared/singleton-owner.mjs +36 -6
- package/src/runtime/shared/skill-mentions.d.mts +10 -0
- package/src/runtime/shared/skill-mentions.mjs +133 -0
- package/src/runtime/shared/skill-script-runtime.mjs +264 -0
- package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
- package/src/runtime/shared/tool-primitives.mjs +35 -2
- package/src/runtime/shared/tool-surface.mjs +7 -0
- package/src/runtime/shared/tool-work-units.mjs +14 -0
- package/src/runtime/shared/transcript-metadata.mjs +17 -1
- package/src/runtime/shared/turn-changes.mjs +791 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
- package/src/runtime/shared/turn-snapshot.mjs +690 -81
- package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
- package/src/runtime/shared/worker-requests.mjs +15 -2
- package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
- package/src/runtime/shared/worktrees/contract.mjs +551 -0
- package/src/runtime/shared/worktrees/disk.mjs +57 -0
- package/src/runtime/shared/worktrees/git.mjs +247 -0
- package/src/runtime/shared/worktrees/include.mjs +480 -0
- package/src/runtime/shared/worktrees/lock.mjs +46 -0
- package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
- package/src/runtime/shared/worktrees/manager.mjs +1215 -0
- package/src/runtime/shared/worktrees/merge.mjs +182 -0
- package/src/runtime/shared/worktrees/owner.mjs +46 -0
- package/src/runtime/shared/worktrees/paths.mjs +62 -0
- package/src/runtime/shared/worktrees/registry.mjs +110 -0
- package/src/runtime/shared/worktrees/settings.mjs +84 -0
- package/src/runtime/shared/worktrees/slug.mjs +62 -0
- package/src/runtime/shared/worktrees/test-support.mjs +96 -0
- package/src/runtime/visualize-bridge/client.mjs +181 -0
- package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
- package/src/session-runtime/automation-agents.mjs +8 -2
- package/src/session-runtime/boot/apis.mjs +90 -0
- package/src/session-runtime/boot/config.mjs +7 -2
- package/src/session-runtime/boot/facade.mjs +21 -0
- package/src/session-runtime/boot/infrastructure.mjs +30 -18
- package/src/session-runtime/boot/session.mjs +1 -0
- package/src/session-runtime/boot/tools.mjs +4 -0
- package/src/session-runtime/config-lifecycle.mjs +10 -1
- package/src/session-runtime/conversation-import/discovery.mjs +698 -0
- package/src/session-runtime/conversation-import/service.mjs +369 -0
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
- package/src/session-runtime/global-extensions.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
- package/src/session-runtime/internal-tool-executor.mjs +9 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
- package/src/session-runtime/mcp-glue/connect.mjs +68 -16
- package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
- package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
- package/src/session-runtime/mcp-glue.mjs +22 -2
- package/src/session-runtime/model-route/route-persist.mjs +3 -0
- package/src/session-runtime/model-route/set-route.mjs +15 -2
- package/src/session-runtime/new-session-config.mjs +3 -2
- package/src/session-runtime/notification-bus.mjs +18 -3
- package/src/session-runtime/provider-auth/accounts.mjs +33 -15
- package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
- package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
- package/src/session-runtime/provider-readiness.mjs +16 -10
- package/src/session-runtime/resource-extension-sync.mjs +45 -7
- package/src/session-runtime/resource-mcp-api.mjs +121 -84
- package/src/session-runtime/resource-plugins-api.mjs +29 -41
- package/src/session-runtime/resource-scope-decorate.mjs +6 -8
- package/src/session-runtime/resource-skills-api.mjs +23 -2
- package/src/session-runtime/route-resolution.mjs +10 -1
- package/src/session-runtime/route-state.mjs +16 -2
- package/src/session-runtime/runtime-core.mjs +5 -0
- package/src/session-runtime/runtime-facade.mjs +8 -0
- package/src/session-runtime/runtime-review-api.mjs +9 -0
- package/src/session-runtime/schedule-session-run.mjs +1 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
- package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
- package/src/session-runtime/services/agent-tool.mjs +1 -0
- package/src/session-runtime/services/channel-session-router.mjs +4 -1
- package/src/session-runtime/services/daemon-candidate.mjs +3 -2
- package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
- package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
- package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
- package/src/session-runtime/services/provider-admin.mjs +64 -18
- package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
- package/src/session-runtime/session-create/session-options.mjs +10 -1
- package/src/session-runtime/session-turn-api.mjs +1 -0
- package/src/session-runtime/settings-api.mjs +4 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
- package/src/session-runtime/settings-worktree-api.mjs +17 -0
- package/src/session-runtime/setup-tool/executor.mjs +23 -5
- package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
- package/src/session-runtime/skills-api.mjs +7 -3
- package/src/session-runtime/turn/agent-ops.mjs +12 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
- package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
- package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
- package/src/session-runtime/turn/turn-run.mjs +6 -7
- package/src/session-runtime/webhook-session-run.mjs +16 -4
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
- package/src/session-runtime/workspace/admission-gate.mjs +197 -0
- package/src/session-runtime/workspace/project-branches.mjs +136 -0
- package/src/session-runtime/workspace/transition-steps.mjs +364 -0
- package/src/session-runtime/workspace/transitions.mjs +651 -0
- package/src/session-runtime/workspace/workspace-state.mjs +112 -0
- package/src/skill-script-entry.mjs +36 -0
- package/src/standalone/channel-transport/http-routes.mjs +17 -0
- package/src/standalone/channel-transport.mjs +13 -1
- package/src/standalone/daemon-desktop-runtime.mjs +1 -0
- package/src/standalone/daemon-owner.mjs +372 -0
- package/src/standalone/daemon-stored-session-views.mjs +15 -4
- package/src/standalone/daemon.mjs +179 -8
- package/src/standalone/desktop-service-registry.mjs +20 -0
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client/attachment-calls.mjs +3 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
- package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
- package/src/standalone/session-client.mjs +104 -13
- package/src/standalone/session-protocol.mjs +21 -0
- package/src/standalone/session-proxy.mjs +3 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
- package/src/standalone/session-service/projection/revisions.mjs +2 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
- package/src/standalone/session-service/projection/workspace.mjs +14 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
- package/src/standalone/session-transport/call-route.mjs +13 -0
- package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
- package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
- package/src/standalone/session-transport/client-registry.mjs +20 -3
- package/src/standalone/session-transport/routes.mjs +79 -2
- package/src/standalone/session-transport/stack.mjs +2 -0
- package/src/standalone/session-transport.mjs +24 -1
- package/src/tui/App.jsx +9 -1
- package/src/tui/app/app-view/floating-panel.jsx +9 -1
- package/src/tui/app/app-view.jsx +6 -0
- package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
- package/src/tui/app/message-selector.mjs +2 -1
- package/src/tui/app/model-options.mjs +37 -5
- package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
- package/src/tui/app/model-picker.mjs +4 -2
- package/src/tui/app/skill-mention-palette.mjs +88 -0
- package/src/tui/app/slash-palette.mjs +23 -7
- package/src/tui/app/transcript-row-estimate.mjs +11 -3
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
- package/src/tui/app/use-prompt-state.mjs +5 -0
- package/src/tui/components/Message.jsx +22 -1
- package/src/tui/components/PromptInput.jsx +21 -3
- package/src/tui/components/SlashCommandPalette.jsx +12 -3
- package/src/tui/components/TranscriptItem.jsx +9 -1
- package/src/tui/components/prompt-input/key-handler.mjs +21 -8
- package/src/tui/components/user-message-skills.mjs +38 -0
- package/src/tui/dist/index.mjs +839 -207
- package/src/tui/index.jsx +10 -2
- package/src/tui/session/compaction-view.mjs +147 -0
- package/src/tui/session/context-state.mjs +6 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/queue-helpers.mjs +7 -2
- package/src/tui/session/quota-resume.mjs +2 -1
- package/src/tui/session/session-api/commands.mjs +10 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +4 -2
- package/src/tui/session/session-api/intake/rewind.mjs +2 -1
- package/src/tui/session/session-api/integrations.mjs +17 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
- package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
- package/src/tui/session/session-api/routes.mjs +10 -1
- package/src/tui/session/session-api/settings.mjs +9 -0
- package/src/tui/session/session-api/worktree.mjs +60 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +31 -12
- package/src/tui/session/session-flow/drain.mjs +27 -3
- package/src/tui/session/session-flow/session-reset.mjs +4 -3
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/transcript-intake.mjs +14 -5
- package/src/tui/session/tui-steering-persist.mjs +25 -9
- package/src/tui/session/turn-stream.mjs +32 -4
- package/src/tui/session/turn-tool-cards.mjs +10 -2
- package/src/tui/session/turn.mjs +56 -10
- package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
- package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
|
@@ -6,7 +6,7 @@ import { tokenizeDirectArgv } from './shell-direct-exe.mjs';
|
|
|
6
6
|
import { GIT_DIFF_EXIT_SIGNAL_FLAGS } from './bash-tool/benign-exit.mjs';
|
|
7
7
|
import { withBuiltinPathLocks } from './path-locks.mjs';
|
|
8
8
|
import { withAdvisoryLocks } from './advisory-lock.mjs';
|
|
9
|
-
import { withGitRepoReadLock, withGitRepoWriteLock } from './git-repo-rw-lock.mjs';
|
|
9
|
+
import { gitRepoLockKey, withGitRepoReadLock, withGitRepoWriteLock } from './git-repo-rw-lock.mjs';
|
|
10
10
|
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
11
11
|
import { drainCodeGraphCache } from '../code-graph-state.mjs';
|
|
12
12
|
import { ensureNativeSpawnServer, tryNativeSpawn } from '../lib/native-spawn-client.mjs';
|
|
@@ -460,22 +460,22 @@ function nearestGitDir(startDir) {
|
|
|
460
460
|
|
|
461
461
|
function cachedRepoRoot(key, plan) {
|
|
462
462
|
if (!key) return null;
|
|
463
|
-
const
|
|
464
|
-
if (!
|
|
463
|
+
const hit = repoRootCache.get(key);
|
|
464
|
+
if (!hit) return null;
|
|
465
465
|
const nearest = nearestGitDir(plan.cwd);
|
|
466
|
-
if (!nearest || resolve(nearest) !== resolve(root)) {
|
|
466
|
+
if (!nearest || resolve(nearest) !== resolve(hit.root)) {
|
|
467
467
|
repoRootCache.delete(key);
|
|
468
468
|
return null;
|
|
469
469
|
}
|
|
470
470
|
repoRootCache.delete(key);
|
|
471
|
-
repoRootCache.set(key,
|
|
472
|
-
return
|
|
471
|
+
repoRootCache.set(key, hit);
|
|
472
|
+
return hit;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
function rememberRepoRoot(key,
|
|
476
|
-
if (!key || !root) return;
|
|
475
|
+
function rememberRepoRoot(key, hit) {
|
|
476
|
+
if (!key || !hit?.root) return;
|
|
477
477
|
repoRootCache.delete(key);
|
|
478
|
-
repoRootCache.set(key,
|
|
478
|
+
repoRootCache.set(key, hit);
|
|
479
479
|
while (repoRootCache.size > REPO_ROOT_CACHE_MAX) {
|
|
480
480
|
const oldest = repoRootCache.keys().next().value;
|
|
481
481
|
if (oldest === undefined) break;
|
|
@@ -483,15 +483,22 @@ function rememberRepoRoot(key, root) {
|
|
|
483
483
|
}
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
// `lockKey` is the repository's canonical common git dir: the main checkout
|
|
487
|
+
// and its linked worktrees share one object store and ref namespace, so their
|
|
488
|
+
// git calls serialize on one repository lock (managed worktree mutations take
|
|
489
|
+
// the same key).
|
|
486
490
|
async function resolveRepo(plan, signal) {
|
|
487
491
|
const key = repoRootCacheKey(plan);
|
|
488
492
|
const hit = cachedRepoRoot(key, plan);
|
|
489
|
-
if (hit) return { root: hit, probe: null };
|
|
490
|
-
const result = await runGit(plan, ['rev-parse', '--show-toplevel'], {
|
|
493
|
+
if (hit) return { root: hit.root, lockKey: hit.lockKey, probe: null };
|
|
494
|
+
const result = await runGit(plan, ['rev-parse', '--show-toplevel', '--path-format=absolute', '--git-common-dir'], {
|
|
495
|
+
signal,
|
|
496
|
+
});
|
|
491
497
|
if (succeeded(result)) {
|
|
492
|
-
const root = cleanText(result.stdout);
|
|
493
|
-
|
|
494
|
-
|
|
498
|
+
const [root, commonDir] = cleanText(result.stdout).split('\n');
|
|
499
|
+
const resolved = { root, lockKey: gitRepoLockKey(commonDir || root) };
|
|
500
|
+
rememberRepoRoot(key, resolved);
|
|
501
|
+
return { ...resolved, probe: result };
|
|
495
502
|
}
|
|
496
503
|
// A bare repository (mirror clone, server-side repo) has no work tree, so
|
|
497
504
|
// `--show-toplevel` refuses; that is still a repository. Answering
|
|
@@ -499,12 +506,13 @@ async function resolveRepo(plan, signal) {
|
|
|
499
506
|
// to inspect objects it already had.
|
|
500
507
|
if (/must be run in a work tree/i.test(String(result.stderr || ''))) {
|
|
501
508
|
const bare = await runGit(plan, ['rev-parse', '--absolute-git-dir'], { signal });
|
|
502
|
-
if (!succeeded(bare)) return { root: null, probe: bare };
|
|
509
|
+
if (!succeeded(bare)) return { root: null, lockKey: null, probe: bare };
|
|
503
510
|
const root = cleanText(bare.stdout);
|
|
504
|
-
|
|
505
|
-
|
|
511
|
+
const resolved = { root, lockKey: gitRepoLockKey(root) };
|
|
512
|
+
rememberRepoRoot(key, resolved);
|
|
513
|
+
return { ...resolved, probe: bare };
|
|
506
514
|
}
|
|
507
|
-
return { root: null, probe: result };
|
|
515
|
+
return { root: null, lockKey: null, probe: result };
|
|
508
516
|
}
|
|
509
517
|
|
|
510
518
|
function localizeConfigPlan(plan) {
|
|
@@ -650,8 +658,9 @@ async function executeGitStage(input, workDir, options = {}) {
|
|
|
650
658
|
const limit = outputLimit(input?.output_limit, 50);
|
|
651
659
|
const signal = abortSignalOf(options);
|
|
652
660
|
const repo = snapshot.repo;
|
|
661
|
+
const { lockKey } = await resolveRepo({ ...snapshot.plan, cwd: repo }, signal);
|
|
653
662
|
return withGitRepoWriteLock(
|
|
654
|
-
repo,
|
|
663
|
+
lockKey || repo,
|
|
655
664
|
() =>
|
|
656
665
|
withGitMutationLocks(repo, async () => {
|
|
657
666
|
const current = await runStageableDiff(snapshot.plan, snapshot.argv, repo, signal);
|
|
@@ -722,7 +731,7 @@ async function executeSingleGitTool(input, workDir, options = {}) {
|
|
|
722
731
|
// ("git rev-parse exited 128") for a `git status` in a plain directory hid
|
|
723
732
|
// git's own "fatal: not a git repository" line behind a command the model
|
|
724
733
|
// never issued — and re-ran the probe just to build that message.
|
|
725
|
-
const { root: repo, probe } = await resolveRepo(plan, signal);
|
|
734
|
+
const { root: repo, lockKey, probe } = await resolveRepo(plan, signal);
|
|
726
735
|
if (!repo) {
|
|
727
736
|
const missing = probe ?? (await runGit(plan, ['rev-parse', '--show-toplevel'], { signal }));
|
|
728
737
|
return commandResult(plan, missing, limit);
|
|
@@ -732,7 +741,7 @@ async function executeSingleGitTool(input, workDir, options = {}) {
|
|
|
732
741
|
if (stageableRequest) prepared.argv.splice(1, 0, '--no-textconv', '--src-prefix=a/', '--dst-prefix=b/');
|
|
733
742
|
if (isReadOnly(plan)) {
|
|
734
743
|
return withGitRepoReadLock(
|
|
735
|
-
|
|
744
|
+
lockKey,
|
|
736
745
|
async () => {
|
|
737
746
|
const result = stageableRequest
|
|
738
747
|
? await runStageableDiff(plan, prepared.argv, repo, signal)
|
|
@@ -754,7 +763,7 @@ async function executeSingleGitTool(input, workDir, options = {}) {
|
|
|
754
763
|
);
|
|
755
764
|
}
|
|
756
765
|
return withGitRepoWriteLock(
|
|
757
|
-
|
|
766
|
+
lockKey,
|
|
758
767
|
() =>
|
|
759
768
|
withGitMutationLocks(repo, async () => {
|
|
760
769
|
const result = await runGit(plan, prepared.argv, { signal });
|
|
@@ -896,4 +905,5 @@ export const _gitCommandInternals = {
|
|
|
896
905
|
localizeConfigPlan,
|
|
897
906
|
parseCommand,
|
|
898
907
|
prepare,
|
|
908
|
+
resolveRepo,
|
|
899
909
|
};
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pathKey } from '../../../../shared/worktrees/paths.mjs';
|
|
2
2
|
|
|
3
3
|
const states = new Map();
|
|
4
4
|
|
|
5
|
+
// Callers key by the canonical `git rev-parse --git-common-dir`, so the main
|
|
6
|
+
// checkout and every linked worktree of one repository share one lock. The key
|
|
7
|
+
// is the real path (8.3 names, symlinked parents) and case-insensitive on
|
|
8
|
+
// Windows, so two spellings of one directory never hold separate locks.
|
|
9
|
+
export function gitRepoLockKey(repo) {
|
|
10
|
+
return pathKey(String(repo || '.'));
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
function keyFor(repo) {
|
|
6
|
-
|
|
7
|
-
return process.platform === 'win32' ? key.toLowerCase() : key;
|
|
14
|
+
return gitRepoLockKey(repo);
|
|
8
15
|
}
|
|
9
16
|
|
|
10
17
|
function stateFor(key) {
|
|
@@ -8,7 +8,7 @@ import { normalizeOutputPath, resolveAgainstCwd } from '../path-utils.mjs';
|
|
|
8
8
|
/** Report the requested path's result; suggestions never retarget the operation. */
|
|
9
9
|
export async function readFamilyPathEnoentOrError(workDir, fullPath, inputPath, err) {
|
|
10
10
|
const safeMsg = normalizeErrorMessage(err instanceof Error ? err.message : String(err));
|
|
11
|
-
const hint = buildNotFoundHint(workDir, fullPath, 'List', err?.code);
|
|
11
|
+
const hint = await buildNotFoundHint(workDir, fullPath, 'List', err?.code);
|
|
12
12
|
const tail = finalizeReadFamilyEnoentTail(hint, inputPath, err?.code);
|
|
13
13
|
// Conclusive absence is the list's ANSWER, not a tool failure (matches
|
|
14
14
|
// read's `[path absent]` and git's `repo:false` policy). Genuine
|
|
@@ -78,4 +78,4 @@ export async function countTextFileLines(fullPath, size) {
|
|
|
78
78
|
} finally {
|
|
79
79
|
await handle?.close().catch(() => {});
|
|
80
80
|
}
|
|
81
|
-
}
|
|
81
|
+
}
|
|
@@ -97,7 +97,7 @@ const BASENAME_SCAN_SKIP_DIRS = new Set([
|
|
|
97
97
|
// directories scanned and wall-clock. `visitEntry` decides what counts as a
|
|
98
98
|
// match and which directories to queue, and returns true to stop reading the
|
|
99
99
|
// current directory (its own limit break).
|
|
100
|
-
function scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, visitEntry) {
|
|
100
|
+
async function scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, visitEntry) {
|
|
101
101
|
const matches = [];
|
|
102
102
|
const queue = [searchRoot];
|
|
103
103
|
const stopAt = Date.now() + Math.max(1, deadlineMs);
|
|
@@ -112,7 +112,7 @@ function scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, visitEntry)
|
|
|
112
112
|
scanned++;
|
|
113
113
|
let entries;
|
|
114
114
|
try {
|
|
115
|
-
entries =
|
|
115
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
116
116
|
} catch {
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
@@ -127,7 +127,7 @@ function scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, visitEntry)
|
|
|
127
127
|
return matches;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
export function findFileByBasename(
|
|
130
|
+
export async function findFileByBasename(
|
|
131
131
|
searchRoot,
|
|
132
132
|
fullPath,
|
|
133
133
|
{ limit = 3, maxDirs = 6000, deadlineMs = BASENAME_SCAN_DEADLINE_MS } = {}
|
|
@@ -136,7 +136,7 @@ export function findFileByBasename(
|
|
|
136
136
|
if (typeof searchRoot !== 'string' || !searchRoot) return [];
|
|
137
137
|
const target = basename(fullPath).toLowerCase();
|
|
138
138
|
if (!target) return [];
|
|
139
|
-
return scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, (dir, ent, matches, queue) => {
|
|
139
|
+
return await scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, (dir, ent, matches, queue) => {
|
|
140
140
|
const name = ent.name;
|
|
141
141
|
if (ent.isDirectory()) {
|
|
142
142
|
if (name.startsWith('.') || BASENAME_SCAN_SKIP_DIRS.has(name)) return false;
|
|
@@ -205,7 +205,7 @@ export function findBySuffixStrip(searchRoot, fullPath, { maxIterations = 12 } =
|
|
|
205
205
|
|
|
206
206
|
// Same as findFileByBasename but for directories sharing the missing path's
|
|
207
207
|
// final segment name (e.g. guessed `src/shared` → the real `lib/shared`).
|
|
208
|
-
export function findDirectoryByBasename(
|
|
208
|
+
export async function findDirectoryByBasename(
|
|
209
209
|
searchRoot,
|
|
210
210
|
fullPath,
|
|
211
211
|
{ limit = 3, maxDirs = 6000, deadlineMs = BASENAME_SCAN_DEADLINE_MS } = {}
|
|
@@ -214,7 +214,7 @@ export function findDirectoryByBasename(
|
|
|
214
214
|
if (typeof searchRoot !== 'string' || !searchRoot) return [];
|
|
215
215
|
const target = basename(fullPath).toLowerCase();
|
|
216
216
|
if (!target) return [];
|
|
217
|
-
return scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, (dir, ent, matches, queue) => {
|
|
217
|
+
return await scanByBasename(searchRoot, { limit, maxDirs, deadlineMs }, (dir, ent, matches, queue) => {
|
|
218
218
|
const name = ent.name;
|
|
219
219
|
if (!ent.isDirectory()) return false;
|
|
220
220
|
if (name.toLowerCase() === target) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The bounded cache validator can certify the whole file only within this
|
|
2
|
+
// byte budget. Larger reads must not reuse metadata-only or prefix-only
|
|
3
|
+
// content/line-position evidence: equal stats can hide edits past the prefix.
|
|
4
|
+
export const READ_PREFIX_HASH_BYTES = 65536;
|
|
5
|
+
|
|
6
|
+
export function canCacheReadFile(stat) {
|
|
7
|
+
return Number.isFinite(stat?.size) && stat.size >= 0 && stat.size <= READ_PREFIX_HASH_BYTES;
|
|
8
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, open, readFile, readdir, stat, unlink } from 'node:fs/promises';
|
|
2
2
|
import { statSync } from 'node:fs';
|
|
3
3
|
import { fileVersion, sameFileVersion } from './file-version.mjs';
|
|
4
|
+
import { canCacheReadFile, READ_PREFIX_HASH_BYTES } from './read-cache-policy.mjs';
|
|
4
5
|
import { hashText as sha256Hex } from './hash-utils.mjs';
|
|
5
6
|
import { join, normalize, resolve } from 'node:path';
|
|
6
7
|
import { getPluginData } from '../../config.mjs';
|
|
@@ -18,6 +19,15 @@ const READ_RANGE_INDEX_SWEEP_CONCURRENCY = 8;
|
|
|
18
19
|
const READ_RANGE_INDEX_CACHE = new Map();
|
|
19
20
|
const READ_RANGE_INDEX_DRAFTS = new WeakMap();
|
|
20
21
|
const READ_RANGE_INDEX_PERSIST_PENDING = new Map();
|
|
22
|
+
const READ_RANGE_INDEX_INFLIGHT = new Map();
|
|
23
|
+
const READ_RANGE_INDEX_LAST_PERSISTED = new Map();
|
|
24
|
+
// key -> outstanding deletes whose unlink / cancelled write has not settled.
|
|
25
|
+
const READ_RANGE_INDEX_DELETING = new Map();
|
|
26
|
+
let readRangeIndexDeleteSeq = 0;
|
|
27
|
+
const READ_RANGE_INDEX_LAST_PERSISTED_MAX = 256;
|
|
28
|
+
const READ_RANGE_INDEX_WRITE_CONCURRENCY = 2;
|
|
29
|
+
const READ_RANGE_INDEX_WRITE_WAITERS = [];
|
|
30
|
+
let readRangeIndexWritesActive = 0;
|
|
21
31
|
let readRangeIndexDiskSwept = false;
|
|
22
32
|
|
|
23
33
|
let traceReadRangeIndex = () => {};
|
|
@@ -83,7 +93,7 @@ function readRangeIndexMatches(row, fullPath, version) {
|
|
|
83
93
|
// `prefixHash` they persist into the anchor index.
|
|
84
94
|
async function computePrefixHashForIndex(fullPath, st, handle = null, prefixBuffer = null) {
|
|
85
95
|
try {
|
|
86
|
-
const cap = Math.min(Number(st?.size) || 0,
|
|
96
|
+
const cap = Math.min(Number(st?.size) || 0, READ_PREFIX_HASH_BYTES);
|
|
87
97
|
if (cap <= 0) return '';
|
|
88
98
|
if (Buffer.isBuffer(prefixBuffer) && prefixBuffer.length === cap) {
|
|
89
99
|
return sha256Hex(prefixBuffer);
|
|
@@ -98,7 +108,9 @@ async function computePrefixHashForIndex(fullPath, st, handle = null, prefixBuff
|
|
|
98
108
|
if (!handle) {
|
|
99
109
|
try {
|
|
100
110
|
await fh.close();
|
|
101
|
-
} catch {
|
|
111
|
+
} catch {
|
|
112
|
+
/* close failure after a completed read is irrelevant */
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
} catch {
|
|
@@ -122,14 +134,21 @@ async function loadReadRangeIndexFromDisk(fullPath, st, handle, prefixBuffer) {
|
|
|
122
134
|
// No existsSync preflight: a missing file surfaces as an ENOENT from
|
|
123
135
|
// readFile below, caught by the same try/catch — one FS pass, not two.
|
|
124
136
|
if (!file || !st) return null;
|
|
137
|
+
// A delete wins over any disk content: while a cancelled write may still be
|
|
138
|
+
// landing the file is invisible, and a read that straddled a delete is void.
|
|
139
|
+
const key = canonicalCachePath(fullPath);
|
|
140
|
+
const deleteSeq = readRangeIndexDeleteSeq;
|
|
141
|
+
const deleted = () => READ_RANGE_INDEX_DELETING.has(key) || readRangeIndexDeleteSeq !== deleteSeq;
|
|
142
|
+
if (deleted()) return null;
|
|
125
143
|
try {
|
|
126
144
|
const row = JSON.parse(await readFile(file, 'utf-8'));
|
|
127
|
-
if (!readRangeIndexMatches(row, fullPath, st)) return null;
|
|
145
|
+
if (deleted() || !readRangeIndexMatches(row, fullPath, st)) return null;
|
|
128
146
|
// The prefix is an extra check, not proof that later byte offsets survived.
|
|
129
147
|
if (typeof row.prefixHash === 'string' && row.prefixHash) {
|
|
130
148
|
const cur = await computePrefixHashForIndex(fullPath, st, handle, prefixBuffer);
|
|
131
149
|
if (!cur || cur !== row.prefixHash) return null;
|
|
132
150
|
}
|
|
151
|
+
if (deleted()) return null;
|
|
133
152
|
const anchors = new Map([[0, 0]]);
|
|
134
153
|
for (const entry of row.anchors) {
|
|
135
154
|
if (!Array.isArray(entry) || entry.length < 2) continue;
|
|
@@ -160,35 +179,133 @@ function currentReadRangeIndex(index) {
|
|
|
160
179
|
return !!index?.published && READ_RANGE_INDEX_CACHE.get(canonicalCachePath(index.fullPath)) === index;
|
|
161
180
|
}
|
|
162
181
|
|
|
163
|
-
function
|
|
182
|
+
function rememberPersisted(key, text) {
|
|
183
|
+
READ_RANGE_INDEX_LAST_PERSISTED.delete(key);
|
|
184
|
+
READ_RANGE_INDEX_LAST_PERSISTED.set(key, text);
|
|
185
|
+
if (READ_RANGE_INDEX_LAST_PERSISTED.size > READ_RANGE_INDEX_LAST_PERSISTED_MAX) {
|
|
186
|
+
READ_RANGE_INDEX_LAST_PERSISTED.delete(READ_RANGE_INDEX_LAST_PERSISTED.keys().next().value);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// job = { index, row }: row is a pre-serialised evicted row (index no longer cached).
|
|
191
|
+
// Returns false only when a write was needed but failed (e.g. lock contended).
|
|
192
|
+
function persistReadRangeIndexSync(job) {
|
|
193
|
+
const { index, row: evictedRow } = job;
|
|
164
194
|
const file = readRangeIndexFilePath(index?.fullPath);
|
|
165
|
-
if (!file || !currentReadRangeIndex(index)) return;
|
|
195
|
+
if (!file || (!evictedRow && !currentReadRangeIndex(index))) return true;
|
|
166
196
|
try {
|
|
167
|
-
if (!sameFileVersion(index.fileVersion, statSync(index.fullPath))) return;
|
|
168
|
-
const row = serialiseReadRangeIndex(index);
|
|
169
|
-
if (row)
|
|
170
|
-
|
|
197
|
+
if (!sameFileVersion(evictedRow?.fileVersion || index.fileVersion, statSync(index.fullPath))) return true;
|
|
198
|
+
const row = evictedRow || serialiseReadRangeIndex(index);
|
|
199
|
+
if (!row) return true;
|
|
200
|
+
const key = canonicalCachePath(index.fullPath);
|
|
201
|
+
const text = JSON.stringify(row);
|
|
202
|
+
if (READ_RANGE_INDEX_LAST_PERSISTED.get(key) === text) return true;
|
|
203
|
+
writeJsonAtomicSync(file, row, { compact: true, lock: true, fsync: false });
|
|
204
|
+
rememberPersisted(key, text);
|
|
205
|
+
return true;
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function acquireReadRangeIndexWriteSlot() {
|
|
212
|
+
if (readRangeIndexWritesActive < READ_RANGE_INDEX_WRITE_CONCURRENCY) {
|
|
213
|
+
readRangeIndexWritesActive += 1;
|
|
214
|
+
return Promise.resolve();
|
|
215
|
+
}
|
|
216
|
+
return new Promise((resolve) => READ_RANGE_INDEX_WRITE_WAITERS.push(resolve));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function releaseReadRangeIndexWriteSlot() {
|
|
220
|
+
const next = READ_RANGE_INDEX_WRITE_WAITERS.shift();
|
|
221
|
+
if (next) next();
|
|
222
|
+
else readRangeIndexWritesActive -= 1;
|
|
171
223
|
}
|
|
172
224
|
|
|
173
|
-
|
|
225
|
+
// One write attempt. `state.epoch` changes when the path is deleted: a write
|
|
226
|
+
// that straddles a delete removes what it just wrote instead of resurrecting it.
|
|
227
|
+
async function persistReadRangeIndexOnce(key, job, state) {
|
|
228
|
+
const { index, row: evictedRow } = job;
|
|
174
229
|
const file = readRangeIndexFilePath(index?.fullPath);
|
|
175
230
|
if (!file || (!evictedRow && !currentReadRangeIndex(index))) return;
|
|
231
|
+
const epoch = state.epoch;
|
|
232
|
+
// Admission precedes every stat/lock so waiting writers hold no file lock.
|
|
233
|
+
await acquireReadRangeIndexWriteSlot();
|
|
176
234
|
try {
|
|
235
|
+
if (state.epoch !== epoch || (!evictedRow && !currentReadRangeIndex(index))) return;
|
|
177
236
|
if (!sameFileVersion(evictedRow?.fileVersion || index.fileVersion, await stat(index.fullPath))) return;
|
|
178
237
|
await mkdir(READ_RANGE_INDEX_DISK_DIR, { recursive: true });
|
|
179
|
-
if (!evictedRow && !currentReadRangeIndex(index)) return;
|
|
238
|
+
if (state.epoch !== epoch || (!evictedRow && !currentReadRangeIndex(index))) return;
|
|
180
239
|
const row = evictedRow || serialiseReadRangeIndex(index);
|
|
181
|
-
if (row)
|
|
182
|
-
|
|
240
|
+
if (!row) return;
|
|
241
|
+
const text = JSON.stringify(row);
|
|
242
|
+
if (READ_RANGE_INDEX_LAST_PERSISTED.get(key) === text) return;
|
|
243
|
+
readRangeIndexPersistStats.concurrent += 1;
|
|
244
|
+
readRangeIndexPersistStats.maxConcurrent = Math.max(
|
|
245
|
+
readRangeIndexPersistStats.maxConcurrent,
|
|
246
|
+
readRangeIndexPersistStats.concurrent
|
|
247
|
+
);
|
|
248
|
+
try {
|
|
249
|
+
await writeJsonAtomicAsync(file, row, { compact: true, lock: true, fsync: false });
|
|
250
|
+
readRangeIndexPersistStats.writes += 1;
|
|
251
|
+
} finally {
|
|
252
|
+
readRangeIndexPersistStats.concurrent -= 1;
|
|
253
|
+
}
|
|
254
|
+
if (state.epoch !== epoch) {
|
|
255
|
+
await unlink(file).catch(() => {});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
rememberPersisted(key, text);
|
|
259
|
+
} catch {
|
|
260
|
+
/* persisting the index is best-effort */
|
|
261
|
+
} finally {
|
|
262
|
+
releaseReadRangeIndexWriteSlot();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Single-flight per cache file: a request during an in-flight write only
|
|
267
|
+
// replaces the pending job; one follow-up write persists the latest state.
|
|
268
|
+
function requestReadRangeIndexPersist(key, job) {
|
|
269
|
+
const state = READ_RANGE_INDEX_INFLIGHT.get(key);
|
|
270
|
+
if (state) {
|
|
271
|
+
state.next = job;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const fresh = { epoch: 0, next: null, cancelWaiters: [] };
|
|
275
|
+
READ_RANGE_INDEX_INFLIGHT.set(key, fresh);
|
|
276
|
+
const settleCancelled = () => {
|
|
277
|
+
for (const resolve of fresh.cancelWaiters.splice(0)) resolve();
|
|
278
|
+
};
|
|
279
|
+
void (async () => {
|
|
280
|
+
try {
|
|
281
|
+
let cur = job;
|
|
282
|
+
while (cur) {
|
|
283
|
+
await persistReadRangeIndexOnce(key, cur, fresh);
|
|
284
|
+
// A cancelled write has finished (and cleaned up) once Once returns.
|
|
285
|
+
settleCancelled();
|
|
286
|
+
cur = fresh.next;
|
|
287
|
+
fresh.next = null;
|
|
288
|
+
}
|
|
289
|
+
} finally {
|
|
290
|
+
settleCancelled();
|
|
291
|
+
READ_RANGE_INDEX_INFLIGHT.delete(key);
|
|
292
|
+
}
|
|
293
|
+
})();
|
|
183
294
|
}
|
|
184
295
|
|
|
185
296
|
export function scheduleReadRangeIndexPersist(index) {
|
|
186
297
|
if (!READ_RANGE_INDEX_DISK_DIR || !currentReadRangeIndex(index)) return;
|
|
187
298
|
const key = canonicalCachePath(index.fullPath);
|
|
299
|
+
const inflight = READ_RANGE_INDEX_INFLIGHT.get(key);
|
|
300
|
+
if (inflight) {
|
|
301
|
+
inflight.next = { index, row: null };
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
188
304
|
if (READ_RANGE_INDEX_PERSIST_PENDING.has(key)) return;
|
|
189
305
|
const t = setTimeout(() => {
|
|
190
306
|
READ_RANGE_INDEX_PERSIST_PENDING.delete(key);
|
|
191
|
-
|
|
307
|
+
const latest = READ_RANGE_INDEX_CACHE.get(key);
|
|
308
|
+
if (latest) requestReadRangeIndexPersist(key, { index: latest, row: null });
|
|
192
309
|
}, READ_RANGE_INDEX_PERSIST_DEBOUNCE_MS);
|
|
193
310
|
t.unref();
|
|
194
311
|
READ_RANGE_INDEX_PERSIST_PENDING.set(key, t);
|
|
@@ -198,10 +315,19 @@ export function flushReadRangeIndexesSync() {
|
|
|
198
315
|
for (const [key, timer] of READ_RANGE_INDEX_PERSIST_PENDING) {
|
|
199
316
|
try {
|
|
200
317
|
clearTimeout(timer);
|
|
201
|
-
} catch {
|
|
318
|
+
} catch {
|
|
319
|
+
/* a timer that cannot be cleared must not block the flush */
|
|
320
|
+
}
|
|
202
321
|
READ_RANGE_INDEX_PERSIST_PENDING.delete(key);
|
|
203
322
|
const index = READ_RANGE_INDEX_CACHE.get(key);
|
|
204
|
-
if (index) persistReadRangeIndexSync(index);
|
|
323
|
+
if (index) persistReadRangeIndexSync({ index, row: null });
|
|
324
|
+
}
|
|
325
|
+
// Writes queued behind an in-flight one are still pending state at exit.
|
|
326
|
+
for (const state of READ_RANGE_INDEX_INFLIGHT.values()) {
|
|
327
|
+
// Keep the job unless it was durably handled: when the async writer holds
|
|
328
|
+
// the lock, its follow-up still persists it if the process keeps running.
|
|
329
|
+
const job = state.next;
|
|
330
|
+
if (job && persistReadRangeIndexSync(job) && state.next === job) state.next = null;
|
|
205
331
|
}
|
|
206
332
|
}
|
|
207
333
|
|
|
@@ -212,11 +338,32 @@ function dropReadRangeIndexSlot(key) {
|
|
|
212
338
|
}
|
|
213
339
|
|
|
214
340
|
export function deleteReadRangeIndexForPath(fullPath) {
|
|
215
|
-
|
|
341
|
+
const key = canonicalCachePath(fullPath);
|
|
342
|
+
dropReadRangeIndexSlot(key);
|
|
343
|
+
READ_RANGE_INDEX_LAST_PERSISTED.delete(key);
|
|
344
|
+
readRangeIndexDeleteSeq += 1;
|
|
345
|
+
// Disk loads for this path are void until the unlink and any cancelled
|
|
346
|
+
// in-flight write (which removes its own output) have settled.
|
|
347
|
+
READ_RANGE_INDEX_DELETING.set(key, (READ_RANGE_INDEX_DELETING.get(key) || 0) + 1);
|
|
348
|
+
const inflight = READ_RANGE_INDEX_INFLIGHT.get(key);
|
|
349
|
+
let cancelled = Promise.resolve();
|
|
350
|
+
if (inflight) {
|
|
351
|
+
inflight.epoch += 1;
|
|
352
|
+
inflight.next = null;
|
|
353
|
+
cancelled = new Promise((resolve) => inflight.cancelWaiters.push(resolve));
|
|
354
|
+
}
|
|
216
355
|
const file = readRangeIndexFilePath(fullPath);
|
|
217
|
-
|
|
356
|
+
const unlinked = file ? unlink(file).catch(() => {}) : Promise.resolve();
|
|
357
|
+
void Promise.all([unlinked, cancelled]).then(() => {
|
|
358
|
+
const left = (READ_RANGE_INDEX_DELETING.get(key) || 1) - 1;
|
|
359
|
+
if (left > 0) READ_RANGE_INDEX_DELETING.set(key, left);
|
|
360
|
+
else READ_RANGE_INDEX_DELETING.delete(key);
|
|
361
|
+
});
|
|
218
362
|
}
|
|
219
363
|
|
|
364
|
+
// Test observability for write coalescing and the concurrency cap.
|
|
365
|
+
export const readRangeIndexPersistStats = { writes: 0, concurrent: 0, maxConcurrent: 0 };
|
|
366
|
+
|
|
220
367
|
export async function sweepStaleReadRangeIndexes(directory = READ_RANGE_INDEX_DISK_DIR) {
|
|
221
368
|
if (!directory) return;
|
|
222
369
|
let entries;
|
|
@@ -234,8 +381,14 @@ export async function sweepStaleReadRangeIndexes(directory = READ_RANGE_INDEX_DI
|
|
|
234
381
|
const full = join(directory, name);
|
|
235
382
|
try {
|
|
236
383
|
const st = await stat(full);
|
|
237
|
-
if (now - st.mtimeMs > READ_RANGE_INDEX_DISK_STALE_MS)
|
|
238
|
-
|
|
384
|
+
if (now - st.mtimeMs > READ_RANGE_INDEX_DISK_STALE_MS) {
|
|
385
|
+
await unlink(full);
|
|
386
|
+
// A removed file invalidates the unchanged-content skip.
|
|
387
|
+
READ_RANGE_INDEX_LAST_PERSISTED.clear();
|
|
388
|
+
}
|
|
389
|
+
} catch {
|
|
390
|
+
/* best-effort sweep: a file that vanished or cannot be removed is skipped */
|
|
391
|
+
}
|
|
239
392
|
}
|
|
240
393
|
}
|
|
241
394
|
await Promise.all(Array.from({ length: Math.min(READ_RANGE_INDEX_SWEEP_CONCURRENCY, entries.length) }, worker));
|
|
@@ -254,7 +407,7 @@ function refreshReadRangeIndexSlot(key, index) {
|
|
|
254
407
|
// their slot immediately. Disk IO must not delay the foreground lookup.
|
|
255
408
|
const row = serialiseReadRangeIndex(evicted);
|
|
256
409
|
dropReadRangeIndexSlot(oldest);
|
|
257
|
-
if (row)
|
|
410
|
+
if (row) requestReadRangeIndexPersist(oldest, { index: evicted, row });
|
|
258
411
|
}
|
|
259
412
|
return index;
|
|
260
413
|
}
|
|
@@ -269,6 +422,8 @@ function freshReadRangeIndex(fullPath, version = null) {
|
|
|
269
422
|
export async function getReadRangeIndex(fullPath, st, handle = null, prefixBuffer = null) {
|
|
270
423
|
const key = canonicalCachePath(fullPath);
|
|
271
424
|
let slot = READ_RANGE_INDEX_CACHE.get(key);
|
|
425
|
+
// Reserve ownership before awaiting the real stat even when the hint is
|
|
426
|
+
// large: a now-small file must not renew a lookup invalidated during IO.
|
|
272
427
|
if (!slot) slot = refreshReadRangeIndexSlot(key, freshReadRangeIndex(fullPath));
|
|
273
428
|
const hint = fileVersion(st);
|
|
274
429
|
const readStat = () => (handle ? handle.stat() : stat(fullPath));
|
|
@@ -280,8 +435,18 @@ export async function getReadRangeIndex(fullPath, st, handle = null, prefixBuffe
|
|
|
280
435
|
} catch {
|
|
281
436
|
return null;
|
|
282
437
|
}
|
|
283
|
-
if (!version
|
|
438
|
+
if (!version) return null;
|
|
284
439
|
version = Object.freeze(version);
|
|
440
|
+
// Metadata and a bounded prefix cannot certify suffix line positions.
|
|
441
|
+
// Large scans own only this call's handle identity and byte-zero draft:
|
|
442
|
+
// never load, hash-validate, or publish cross-read anchors/counts.
|
|
443
|
+
if (!canCacheReadFile(version)) {
|
|
444
|
+
if (slot && READ_RANGE_INDEX_CACHE.get(key) === slot) dropReadRangeIndexSlot(key);
|
|
445
|
+
const draft = freshReadRangeIndex(fullPath, version);
|
|
446
|
+
READ_RANGE_INDEX_DRAFTS.set(draft, { slot: null, version, fullPath });
|
|
447
|
+
return draft;
|
|
448
|
+
}
|
|
449
|
+
if (READ_RANGE_INDEX_CACHE.get(key) !== slot) return null;
|
|
285
450
|
if (!slot.fileVersion) slot.fileVersion = version;
|
|
286
451
|
if (!sameFileVersion(slot.fileVersion, version)) {
|
|
287
452
|
dropReadRangeIndexSlot(key);
|
|
@@ -314,27 +479,32 @@ export async function getReadRangeIndex(fullPath, st, handle = null, prefixBuffe
|
|
|
314
479
|
anchors: new Map(source.anchors),
|
|
315
480
|
...(Number.isFinite(source.totalLines) ? { totalLines: source.totalLines } : {}),
|
|
316
481
|
};
|
|
317
|
-
READ_RANGE_INDEX_DRAFTS.set(draft, { slot, version });
|
|
482
|
+
READ_RANGE_INDEX_DRAFTS.set(draft, { slot, version, fullPath });
|
|
318
483
|
return draft;
|
|
319
484
|
}
|
|
320
485
|
|
|
321
|
-
// Successful readers publish once, before closing their handle.
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
|
|
486
|
+
// Successful small-file readers publish once, before closing their handle.
|
|
487
|
+
// Large or detached scans remain private, but terminal validation can still
|
|
488
|
+
// certify their producer identity via onValidated. Post-stat failures never
|
|
489
|
+
// authorize relabelling bytes with a later version.
|
|
490
|
+
export async function publishReadRangeIndex(index, handle, signal = null, beforePublish = null, onValidated = null) {
|
|
325
491
|
const owned = READ_RANGE_INDEX_DRAFTS.get(index);
|
|
326
492
|
if (!owned) return false;
|
|
327
493
|
READ_RANGE_INDEX_DRAFTS.delete(index);
|
|
328
|
-
const { slot, version } = owned;
|
|
329
|
-
const key = canonicalCachePath(
|
|
330
|
-
if (signal?.aborted
|
|
494
|
+
const { slot, version, fullPath } = owned;
|
|
495
|
+
const key = canonicalCachePath(fullPath);
|
|
496
|
+
if (signal?.aborted) return false;
|
|
497
|
+
// Detached cache-only publishers must not wait on validation IO. A streamed
|
|
498
|
+
// result requesting producer certification still owes its terminal stats.
|
|
499
|
+
if (slot && READ_RANGE_INDEX_CACHE.get(key) !== slot && !onValidated) return false;
|
|
331
500
|
try {
|
|
332
|
-
if (!sameFileVersion(version, await handle.stat()) || !sameFileVersion(version, await stat(
|
|
333
|
-
return false;
|
|
501
|
+
if (!sameFileVersion(version, await handle.stat()) || !sameFileVersion(version, await stat(fullPath))) return false;
|
|
334
502
|
} catch {
|
|
335
503
|
return false;
|
|
336
504
|
}
|
|
337
|
-
if (signal?.aborted
|
|
505
|
+
if (signal?.aborted) return false;
|
|
506
|
+
onValidated?.(fileVersion(version));
|
|
507
|
+
if (!slot || READ_RANGE_INDEX_CACHE.get(key) !== slot) return false;
|
|
338
508
|
if (slot.prefixHash && index.prefixHash && slot.prefixHash !== index.prefixHash) return false;
|
|
339
509
|
// The reader's synchronous deadline/cancellation decision belongs after all
|
|
340
510
|
// awaited validation and outside its best-effort stat catch. Once committed,
|