mixdog 1.0.11 → 1.1.0
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/NOTICE.md +20 -0
- package/README.md +75 -181
- package/package.json +9 -4
- package/scripts/build-shipped-history.mjs +131 -0
- package/scripts/check-changelog-locales.mjs +203 -0
- package/scripts/computer-task-cost-report.mjs +0 -4
- package/scripts/lib/run-node-tests.mjs +235 -13
- package/scripts/lib/test-failure-records.mjs +5 -0
- package/scripts/lib/test-failure-summary.mjs +9 -3
- package/scripts/lib/test-spawn-ledger.mjs +19 -0
- package/scripts/prune-embedding-runtime.mjs +9 -5
- package/scripts/sync-client-version-floors.mjs +100 -0
- package/scripts/test-direct.mjs +16 -1
- package/src/agents/advisor/AGENT.md +38 -0
- package/src/agents/advisor/agent.json +6 -0
- package/src/agents/reviewer/AGENT.md +5 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +381 -0
- package/src/defaults/skills/docx/SKILL.md +7 -5
- package/src/defaults/skills/docx/references/html.md +9 -8
- package/src/defaults/skills/docx/references/native-authoring.md +28 -18
- package/src/defaults/skills/goal-management/SKILL.md +11 -5
- package/src/defaults/skills/local-provider/SKILL.md +2 -1
- package/src/defaults/skills/pdf/SKILL.md +9 -6
- package/src/defaults/skills/pdf/references/html.md +17 -12
- package/src/defaults/skills/pptx/SKILL.md +8 -6
- package/src/defaults/skills/pptx/references/composition.md +4 -4
- package/src/defaults/skills/pptx/references/concepts.md +168 -0
- package/src/defaults/skills/pptx/references/direction.md +5 -4
- package/src/defaults/skills/pptx/references/html.md +6 -29
- package/src/defaults/skills/pptx/references/kit-index.md +11 -3
- package/src/defaults/skills/pptx/references/kit.md +27 -5
- package/src/defaults/skills/pptx/references/review.md +6 -5
- package/src/defaults/skills/setup/SKILL.md +5 -8
- package/src/defaults/skills/setup/references/actions.md +61 -31
- package/src/defaults/skills/setup/references/surfaces.md +12 -10
- package/src/defaults/skills/xlsx/SKILL.md +8 -6
- package/src/defaults/skills/xlsx/references/html.md +8 -4
- package/src/defaults/skills/xlsx/references/model-conventions.md +2 -0
- package/src/defaults/skills/xlsx/references/report-design.md +32 -16
- package/src/help.mjs +1 -1
- package/src/lib/rules-builder.cjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
- package/src/runtime/agent/orchestrator/config.mjs +102 -21
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +90 -26
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +79 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -22
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +28 -10
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +14 -72
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +19 -3
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +20 -10
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-sink.mjs +36 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +11 -17
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +138 -30
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +32 -15
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +39 -5
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/lib/note-error-usage.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +57 -0
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +12 -5
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +20 -15
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +56 -11
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +28 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +27 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +26 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +34 -15
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +0 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +0 -19
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +12 -43
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +39 -9
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +31 -9
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +13 -38
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +40 -9
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +7 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +28 -101
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +8 -4
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/openai-service-tier.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/openai-startup-prewarm.mjs +5 -195
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +9 -18
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +4 -66
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/acquire-attempt.mjs +3 -15
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/attempt-request.mjs +6 -56
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +1 -19
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure-stamps.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts.mjs +3 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-outcome.mjs +10 -89
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-span.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-trace.mjs +0 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +34 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +3 -7
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +44 -19
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +40 -12
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -3
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +11 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +31 -41
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
- package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
- package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -2
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +45 -2
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/eager-dispatch/entry.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +6 -13
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +114 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +5 -9
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +51 -18
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +6 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
- package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +10 -28
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +10 -19
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -3
- package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +18 -8
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -11
- package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
- package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
- package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
- package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
- package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -0
- package/src/runtime/channels/lib/config.mjs +3 -8
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
- package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
- package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
- package/src/runtime/computer-bridge/actions.mjs +2 -0
- package/src/runtime/effort-judge/judge-client.mjs +125 -25
- package/src/runtime/effort-judge/judge-input.mjs +96 -0
- package/src/runtime/effort-judge/judge-worker.mjs +52 -20
- package/src/runtime/effort-judge/model-install.mjs +74 -0
- package/src/runtime/effort-judge/model-manifest.json +21 -0
- package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
- package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
- package/src/runtime/github/tool.mjs +214 -14
- package/src/runtime/local-provider/context-settings.mjs +15 -6
- package/src/runtime/local-provider/gguf-header.mjs +13 -5
- package/src/runtime/local-provider/hugging-face.mjs +27 -5
- package/src/runtime/local-provider/install-progress.mjs +7 -0
- package/src/runtime/local-provider/managed-runtime.mjs +2 -1
- package/src/runtime/local-provider/model-maintenance.mjs +15 -6
- package/src/runtime/local-provider/resumable-installations.mjs +40 -1
- package/src/runtime/local-provider/server.mjs +2 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
- package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
- package/src/runtime/memory/lib/memory-embed.mjs +5 -0
- package/src/runtime/memory/lib/pg/process.mjs +57 -5
- package/src/runtime/office/README.md +13 -7
- package/src/runtime/office/authoring/docx-html-build.mjs +4 -4
- package/src/runtime/office/authoring/docx-html-measure.mjs +60 -0
- package/src/runtime/office/authoring/pdf-author-action.mjs +6 -1
- package/src/runtime/office/authoring/pptx-brief.mjs +40 -1
- package/src/runtime/office/authoring/pptx-html-build.mjs +1 -1
- package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
- package/src/runtime/office/bench/deck-bench.mjs +1 -1
- package/src/runtime/office/com/office-com-host.ps1 +8 -0
- package/src/runtime/office/core/office-actions-render.mjs +2 -0
- package/src/runtime/office/design/design-art-direction.mjs +10 -2
- package/src/runtime/office/design/design-discipline.mjs +4 -5
- package/src/runtime/office/design/design-tokens.mjs +20 -1
- package/src/runtime/office/design/docx/design-docx/front-matter.mjs +7 -4
- package/src/runtime/office/design/docx/design-docx/section.mjs +3 -1
- package/src/runtime/office/design/docx/design-docx/writer.mjs +1 -0
- package/src/runtime/office/design/docx/design-docx-components.mjs +19 -15
- package/src/runtime/office/design/docx/design-docx.mjs +5 -6
- package/src/runtime/office/design/docx/document-typography.mjs +20 -6
- package/src/runtime/office/design/xlsx/design-xlsx.mjs +9 -2
- package/src/runtime/office/pdf/document-preview.mjs +133 -12
- package/src/runtime/office/pdf/pdf-writer.mjs +56 -22
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +46 -3
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +27 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +21 -5
- package/src/runtime/office/quality/assurance-structure-docx.mjs +41 -9
- package/src/runtime/office/quality/design-aesthetics.mjs +4 -2
- package/src/runtime/office/quality/design-deck-diversity.mjs +15 -3
- package/src/runtime/office/quality/design-review.mjs +13 -5
- package/src/runtime/office/quality/document-brief.mjs +8 -2
- package/src/runtime/office/quality/forced-break-pages.mjs +203 -0
- package/src/runtime/office/quality/pptx-deck-rubric.mjs +21 -3
- package/src/runtime/office/quality/quality-pipeline.mjs +14 -17
- package/src/runtime/session-search/session-search-db.mjs +183 -0
- package/src/runtime/session-search/session-search-ingest.mjs +106 -0
- package/src/runtime/shared/agent-route-config.mjs +50 -2
- package/src/runtime/shared/bounded-file-read.mjs +166 -0
- package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
- package/src/runtime/shared/config.mjs +24 -4
- package/src/runtime/shared/defaults-separation.mjs +263 -0
- package/src/runtime/shared/error-presentation.mjs +8 -1
- package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
- package/src/runtime/shared/llm/cost.mjs +4 -2
- package/src/runtime/shared/llm/model-catalog-projection.mjs +1 -1
- package/src/runtime/shared/llm/model-catalog.mjs +25 -0
- package/src/runtime/shared/llm/reasoning-usage.mjs +1 -2
- package/src/runtime/shared/llm/usage-accounting.mjs +14 -4
- package/src/runtime/shared/llm/usage-context.mjs +11 -1
- package/src/runtime/shared/llm/usage-ledger-repair.mjs +3 -1
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
- package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
- package/src/runtime/shared/orchestration.mjs +14 -6
- package/src/runtime/shared/pristine-execution-contract.json +7 -2
- package/src/runtime/shared/projects.mjs +65 -48
- package/src/runtime/shared/provider-accounts.mjs +47 -6
- package/src/runtime/shared/quota-resume-store.mjs +255 -0
- package/src/runtime/shared/shipped-definitions.mjs +275 -0
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +30 -9
- package/src/runtime/shared/tool-card-model/aggregate-card.mjs +7 -3
- package/src/runtime/shared/tool-card-model/background-task.mjs +17 -1
- package/src/runtime/shared/tool-card-model/card-detail.mjs +10 -4
- package/src/runtime/shared/tool-card-model/card-header.mjs +10 -4
- package/src/runtime/shared/tool-card-model/card-status.mjs +22 -5
- package/src/runtime/shared/tool-card-model/displayed-result.mjs +6 -1
- package/src/runtime/shared/tool-card-model/media-result.mjs +53 -0
- package/src/runtime/shared/tool-card-model/terminal-status.mjs +17 -1
- package/src/runtime/shared/tool-card-model.golden-cases.mjs +1 -1
- package/src/runtime/shared/tool-card-model.golden.json +75 -66
- package/src/runtime/shared/tool-card-model.mjs +1 -0
- package/src/runtime/shared/tool-output-limit.mjs +3 -0
- package/src/runtime/shared/tool-primitives.mjs +24 -18
- package/src/runtime/shared/tool-result-summary.mjs +128 -17
- package/src/runtime/shared/tool-status.mjs +20 -7
- package/src/runtime/shared/tool-surface.mjs +105 -28
- package/src/runtime/shared/tool-work-units.mjs +221 -18
- package/src/runtime/shared/transcript-metadata.mjs +2 -0
- package/src/runtime/shared/turn-review-reply.mjs +2 -0
- package/src/runtime/shared/turn-snapshot.mjs +65 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +202 -22
- package/src/runtime/shared/worker-requests.mjs +14 -2
- package/src/runtime/terminal-bridge/client.mjs +311 -0
- package/src/runtime/terminal-bridge/tool-defs.mjs +36 -0
- package/src/runtime/web-search/lib/ssrf-guard.mjs +12 -56
- package/src/runtime/web-search/tool-defs.mjs +2 -0
- package/src/session-runtime/boot/apis.mjs +30 -5
- package/src/session-runtime/boot/config.mjs +4 -0
- package/src/session-runtime/boot/facade.mjs +4 -1
- package/src/session-runtime/boot/infrastructure.mjs +21 -2
- package/src/session-runtime/boot/session.mjs +5 -5
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/builtin-features.d.mts +1 -0
- package/src/session-runtime/channel-config-api.mjs +5 -0
- package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
- package/src/session-runtime/config-lifecycle.mjs +31 -6
- package/src/session-runtime/goal-facade-api.mjs +4 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +15 -3
- package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +9 -0
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
- package/src/session-runtime/lifecycle/teardown.mjs +5 -0
- package/src/session-runtime/local-provider-settings.mjs +10 -0
- package/src/session-runtime/media-api.mjs +32 -22
- package/src/session-runtime/model-recency.mjs +14 -0
- package/src/session-runtime/model-route/route-persist.mjs +17 -2
- package/src/session-runtime/model-route/route-tuning.mjs +16 -5
- package/src/session-runtime/model-route/set-route.mjs +55 -22
- package/src/session-runtime/model-route/web-search-route.mjs +2 -2
- package/src/session-runtime/model-route-api.mjs +2 -1
- package/src/session-runtime/notification-bus.mjs +31 -6
- package/src/session-runtime/provider-auth/login.mjs +6 -0
- package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +36 -10
- package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
- package/src/session-runtime/resource-api.mjs +8 -1
- package/src/session-runtime/route-state.mjs +9 -0
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +20 -1
- package/src/session-runtime/runtime-feature-gates.mjs +2 -0
- package/src/session-runtime/runtime-review-api.mjs +12 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +14 -1
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +10 -15
- package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +6 -1
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +20 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +14 -6
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +12 -1
- package/src/session-runtime/services/agent-tool/worker-index.mjs +17 -1
- package/src/session-runtime/services/agent-tool.mjs +1 -0
- package/src/session-runtime/services/channel-admin.mjs +87 -13
- package/src/session-runtime/services/provider-admin.mjs +306 -66
- package/src/session-runtime/services/seeds.mjs +73 -142
- package/src/session-runtime/services/usage-dashboard-model.mjs +29 -0
- package/src/session-runtime/services/usage-dashboard.mjs +7 -0
- package/src/session-runtime/session-address.mjs +125 -0
- package/src/session-runtime/session-create.mjs +57 -2
- package/src/session-runtime/settings-api.mjs +1 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +15 -10
- package/src/session-runtime/settings-compaction-api.mjs +13 -0
- package/src/session-runtime/setup-tool/executor.mjs +93 -40
- package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
- package/src/session-runtime/setup-tool/settings-contract.mjs +46 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +14 -6
- package/src/session-runtime/tool-surface.mjs +16 -9
- package/src/session-runtime/turn/session-ops.mjs +3 -0
- package/src/session-runtime/turn/share-ops.mjs +4 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
- package/src/session-runtime/usage-stats-api.mjs +43 -3
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +6 -2
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
- package/src/standalone/daemon-boot-coordinator.mjs +10 -0
- package/src/standalone/daemon-desktop-runtime.mjs +1 -0
- package/src/standalone/daemon-drain-policy.mjs +9 -0
- package/src/standalone/daemon-stored-session-views.mjs +7 -8
- package/src/standalone/daemon.mjs +36 -8
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client.mjs +52 -7
- package/src/standalone/session-protocol.mjs +9 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
- package/src/standalone/session-service/entries/session-loads.mjs +9 -0
- package/src/standalone/session-service/projection/revisions.mjs +28 -4
- package/src/standalone/session-service/projection.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +140 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
- package/src/standalone/session-service/session-calls.mjs +10 -2
- package/src/standalone/session-service/stored-reader.mjs +26 -1
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-transport/routes.mjs +2 -0
- package/src/tui/app/doctor.mjs +7 -11
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
- package/src/tui/app/model-picker/model-footer.mjs +2 -2
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +21 -2
- package/src/tui/app/model-picker/route-selection.mjs +6 -1
- package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
- package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/slash-dispatch/route-commands.mjs +5 -3
- package/src/tui/app/transcript-row-estimate.mjs +17 -5
- package/src/tui/components/ContextPanel.jsx +5 -1
- package/src/tui/components/Spinner.jsx +34 -319
- package/src/tui/components/SpinnerByline.jsx +256 -0
- package/src/tui/components/tool-execution/aggregate-card.jsx +4 -2
- package/src/tui/components/tool-execution/text-format.mjs +1 -0
- package/src/tui/components/use-spinner-progress.mjs +113 -0
- package/src/tui/dist/index.mjs +15896 -14452
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
- package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
- package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
- package/src/tui/session/agent-job-feed.mjs +4 -0
- package/src/tui/session/context-state.mjs +13 -1
- package/src/tui/session/initial-state.mjs +6 -0
- package/src/tui/session/quota-resume.mjs +263 -0
- package/src/tui/session/session-api/intake/abort.mjs +39 -35
- package/src/tui/session/session-api/intake/submission.mjs +13 -21
- package/src/tui/session/session-api/intake.mjs +3 -3
- package/src/tui/session/session-api/integrations.mjs +5 -1
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
- package/src/tui/session/session-api/lifecycle.mjs +71 -2
- package/src/tui/session/session-api/media.mjs +2 -2
- package/src/tui/session/session-api/quota-resume.mjs +21 -0
- package/src/tui/session/session-api/routes.mjs +5 -2
- package/src/tui/session/session-api/settings.mjs +2 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +14 -6
- package/src/tui/session/session-flow/drain.mjs +52 -5
- package/src/tui/session/session-flow/queue/take-entries.mjs +9 -4
- package/src/tui/session/session-flow/queue.mjs +4 -0
- package/src/tui/session/session-flow/steering.mjs +11 -0
- package/src/tui/session/session-flow.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +7 -2
- package/src/tui/session/transcript-intake.mjs +1 -0
- package/src/tui/session/turn-tool-cards.mjs +4 -6
- package/src/tui/session/turn.mjs +40 -4
- package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-warmup.mjs +0 -165
|
@@ -134,7 +134,7 @@ export function changeProviderAccounts(provider, change) {
|
|
|
134
134
|
!change ||
|
|
135
135
|
typeof change !== 'object' ||
|
|
136
136
|
Array.isArray(change) ||
|
|
137
|
-
Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'rename'].includes(key))
|
|
137
|
+
Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'useCredits', 'rename'].includes(key))
|
|
138
138
|
) {
|
|
139
139
|
throw new TypeError('Invalid provider account change.');
|
|
140
140
|
}
|
|
@@ -175,6 +175,10 @@ export function changeProviderAccounts(provider, change) {
|
|
|
175
175
|
if (typeof change.auto !== 'boolean') throw new TypeError('Automatic account switching must be boolean.');
|
|
176
176
|
pool.auto = change.auto;
|
|
177
177
|
}
|
|
178
|
+
if (change.useCredits !== undefined) {
|
|
179
|
+
if (typeof change.useCredits !== 'boolean') throw new TypeError('Credit use must be boolean.');
|
|
180
|
+
pool.useCredits = change.useCredits;
|
|
181
|
+
}
|
|
178
182
|
});
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -217,7 +221,9 @@ const MAX_BLOCK_MS = 6 * 60 * 60_000;
|
|
|
217
221
|
// account-wide, as before. `source` records whether the server scheduled the
|
|
218
222
|
// window or we inferred it, which decides whether re-measuring the account
|
|
219
223
|
// early can tell us anything.
|
|
220
|
-
|
|
224
|
+
// `options.quota` records that the block came from a typed quota refusal or an
|
|
225
|
+
// exhausted usage window, so later sends still count it as quota evidence.
|
|
226
|
+
export function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
|
|
221
227
|
return update(provider, (pool) => {
|
|
222
228
|
const row = pool.accounts.find((entry) => entry.id === id);
|
|
223
229
|
if (!row) return;
|
|
@@ -227,6 +233,8 @@ export function blockProviderAccount(provider, id, until, model = '', source = '
|
|
|
227
233
|
else delete row.blockedModel;
|
|
228
234
|
if (source === 'retry-after') row.blockedSource = source;
|
|
229
235
|
else delete row.blockedSource;
|
|
236
|
+
if (options.quota === true) row.blockedQuota = true;
|
|
237
|
+
else delete row.blockedQuota;
|
|
230
238
|
});
|
|
231
239
|
}
|
|
232
240
|
|
|
@@ -248,6 +256,7 @@ function clearAccountBlock(row) {
|
|
|
248
256
|
delete row.blockedUntil;
|
|
249
257
|
delete row.blockedModel;
|
|
250
258
|
delete row.blockedSource;
|
|
259
|
+
delete row.blockedQuota;
|
|
251
260
|
}
|
|
252
261
|
|
|
253
262
|
// Single-token weekly labels ("7D Fable", "7D Opus") belong to that Claude
|
|
@@ -256,7 +265,7 @@ function clearAccountBlock(row) {
|
|
|
256
265
|
// model.
|
|
257
266
|
const FAMILY_QUOTA_WINDOW = /^7D\s+([A-Za-z][A-Za-z0-9]*)$/i;
|
|
258
267
|
|
|
259
|
-
function quotaWindowAppliesToModel(window, model) {
|
|
268
|
+
export function quotaWindowAppliesToModel(window, model) {
|
|
260
269
|
const match = FAMILY_QUOTA_WINDOW.exec(String(window?.label || '').trim());
|
|
261
270
|
if (!match) return true;
|
|
262
271
|
const family = match[1].toLowerCase();
|
|
@@ -280,8 +289,36 @@ function blockAppliesToModel(row, model) {
|
|
|
280
289
|
return scoped.some((window) => quotaWindowAppliesToModel(window, scope) && quotaWindowAppliesToModel(window, model));
|
|
281
290
|
}
|
|
282
291
|
|
|
283
|
-
|
|
292
|
+
// Earliest moment any of the given exhausted accounts can serve `model` again,
|
|
293
|
+
// or null when unknown. Per account the LATEST blocking reset counts (every
|
|
294
|
+
// blocking window must clear); across accounts the EARLIEST account does. A
|
|
295
|
+
// server-scheduled block (`retry-after`) is a known time; an inferred block or
|
|
296
|
+
// a full window without a reset time leaves that account's reset unknown.
|
|
297
|
+
export function providerAccountsResetAt(rows, now = Date.now(), model = '', options = {}) {
|
|
298
|
+
const known = [];
|
|
299
|
+
for (const row of rows || []) {
|
|
300
|
+
if (!providerAccountExhausted(row, now, model, options)) continue;
|
|
301
|
+
const times = [];
|
|
302
|
+
let unknown = false;
|
|
303
|
+
if (row.blockedUntil > now && blockAppliesToModel(row, model)) {
|
|
304
|
+
if (row.blockedSource === 'retry-after') times.push(row.blockedUntil);
|
|
305
|
+
else unknown = true;
|
|
306
|
+
}
|
|
307
|
+
if (options.useCredits !== true) {
|
|
308
|
+
for (const window of row.usage?.windows || []) {
|
|
309
|
+
if (!(window.usedPct >= 100) || !quotaWindowAppliesToModel(window, model)) continue;
|
|
310
|
+
if (window.resetAt > now) times.push(window.resetAt);
|
|
311
|
+
else if (!window.resetAt && now - row.usage.checkedAt < 60_000) unknown = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!unknown && times.length) known.push(Math.max(...times));
|
|
315
|
+
}
|
|
316
|
+
return known.length ? Math.min(...known) : null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function providerAccountExhausted(row, now = Date.now(), model = '', options = {}) {
|
|
284
320
|
if (row?.blockedUntil > now && blockAppliesToModel(row, model)) return true;
|
|
321
|
+
if (options.useCredits === true) return false;
|
|
285
322
|
return (row?.usage?.windows || []).some(
|
|
286
323
|
(window) =>
|
|
287
324
|
window.usedPct >= 100 &&
|
|
@@ -309,9 +346,13 @@ function accountHeadroom(row, now, model) {
|
|
|
309
346
|
}
|
|
310
347
|
|
|
311
348
|
export function chooseProviderAccount(pool, excluded = new Set(), now = Date.now(), model = '') {
|
|
312
|
-
const
|
|
349
|
+
const options = { useCredits: pool.useCredits === true };
|
|
350
|
+
const usable = (row) => row && !excluded.has(row.id) && !providerAccountExhausted(row, now, model, options);
|
|
313
351
|
const selected = pool.accounts.find((row) => row.id === pool.selectedId);
|
|
314
|
-
if (pool.auto === false)
|
|
352
|
+
if (pool.auto === false) {
|
|
353
|
+
if (excluded.has(selected?.id)) return null;
|
|
354
|
+
return options.useCredits || usable(selected) ? selected || null : null;
|
|
355
|
+
}
|
|
315
356
|
if (usable(selected)) return selected;
|
|
316
357
|
// Falling back in roster order is blind: the account after the one that just
|
|
317
358
|
// refused may itself be nearly spent, so the request pays a second switch to
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quota-resume-store.mjs — the daemon's durable queue of Lead turns waiting
|
|
3
|
+
* for their provider quota to reset, plus the global auto-resume setting.
|
|
4
|
+
*
|
|
5
|
+
* One process (the daemon) owns the file; every session runtime it hosts and
|
|
6
|
+
* its resume scheduler read and mutate this same in-memory copy, and every
|
|
7
|
+
* mutation is written through synchronously so a restart finds the queue
|
|
8
|
+
* exactly as the last mutation left it. Mutations are synchronous on purpose:
|
|
9
|
+
* a cancel and a fire can never interleave inside one, and each entry carries
|
|
10
|
+
* a generation so a stale holder can neither fire nor defer a replaced entry.
|
|
11
|
+
*
|
|
12
|
+
* Entry: { sessionId, turnKey, provider, model, resetAt, attempts, generation,
|
|
13
|
+
* routeProvider, routeModel }
|
|
14
|
+
* resetAt — when the scheduler next takes a usage reading (the provider's
|
|
15
|
+
* reset time, or the later re-check after a still-exhausted one).
|
|
16
|
+
* Suspension: { sessionId, turnKey, goalId } — the Goal one quota failure
|
|
17
|
+
* moved from active to usage_limited, the only Goal a resume may revive.
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { resolvePluginData } from './plugin-paths.mjs';
|
|
22
|
+
import { writeJsonAtomicSync } from './atomic-file.mjs';
|
|
23
|
+
import { readSection, updateSection } from './config.mjs';
|
|
24
|
+
|
|
25
|
+
export const QUOTA_RESUME_FILE = 'quota-resume.json';
|
|
26
|
+
const FILE_VERSION = 1;
|
|
27
|
+
const SETTING_SECTION = 'quotaResume';
|
|
28
|
+
|
|
29
|
+
let state = null;
|
|
30
|
+
const listeners = new Set();
|
|
31
|
+
// Frozen public snapshots keyed by the immutable entry object they describe,
|
|
32
|
+
// so an unchanged entry publishes the same value identity on every read.
|
|
33
|
+
const publicSnapshots = new WeakMap();
|
|
34
|
+
|
|
35
|
+
const text = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
36
|
+
const finite = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
37
|
+
|
|
38
|
+
function normalizeEntry(value) {
|
|
39
|
+
const entry = {
|
|
40
|
+
sessionId: text(value?.sessionId),
|
|
41
|
+
turnKey: text(value?.turnKey),
|
|
42
|
+
provider: text(value?.provider),
|
|
43
|
+
model: typeof value?.model === 'string' ? value.model : '',
|
|
44
|
+
resetAt: value?.resetAt,
|
|
45
|
+
attempts: Number.isSafeInteger(value?.attempts) && value.attempts >= 0 ? value.attempts : 0,
|
|
46
|
+
generation: value?.generation,
|
|
47
|
+
// The session's own route when the resume was queued; a fire on any
|
|
48
|
+
// other route is not the resume of this failure.
|
|
49
|
+
routeProvider: typeof value?.routeProvider === 'string' ? value.routeProvider : '',
|
|
50
|
+
routeModel: typeof value?.routeModel === 'string' ? value.routeModel : '',
|
|
51
|
+
};
|
|
52
|
+
if (!entry.sessionId || !entry.turnKey || !entry.provider || !finite(entry.resetAt)) return null;
|
|
53
|
+
if (!Number.isSafeInteger(entry.generation) || entry.generation < 1) return null;
|
|
54
|
+
return Object.freeze(entry);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeSuspension(value) {
|
|
58
|
+
const suspension = {
|
|
59
|
+
sessionId: text(value?.sessionId),
|
|
60
|
+
turnKey: text(value?.turnKey),
|
|
61
|
+
goalId: text(value?.goalId),
|
|
62
|
+
};
|
|
63
|
+
return suspension.sessionId && suspension.turnKey && suspension.goalId ? Object.freeze(suspension) : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function storePath() {
|
|
67
|
+
return join(resolvePluginData(), QUOTA_RESUME_FILE);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function load() {
|
|
71
|
+
const path = storePath();
|
|
72
|
+
if (state?.path === path) return state;
|
|
73
|
+
let raw = null;
|
|
74
|
+
try {
|
|
75
|
+
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// A missing or torn file is an empty queue; an unreadable one is not.
|
|
78
|
+
if (error?.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error;
|
|
79
|
+
}
|
|
80
|
+
const entries = new Map();
|
|
81
|
+
for (const value of Array.isArray(raw?.entries) ? raw.entries : []) {
|
|
82
|
+
const entry = normalizeEntry(value);
|
|
83
|
+
if (entry) entries.set(entry.sessionId, entry);
|
|
84
|
+
}
|
|
85
|
+
const suspensions = new Map();
|
|
86
|
+
for (const value of Array.isArray(raw?.suspensions) ? raw.suspensions : []) {
|
|
87
|
+
const suspension = normalizeSuspension(value);
|
|
88
|
+
if (suspension) suspensions.set(suspension.sessionId, suspension);
|
|
89
|
+
}
|
|
90
|
+
const generations = [Number.isSafeInteger(raw?.generation) ? raw.generation : 0];
|
|
91
|
+
for (const entry of entries.values()) generations.push(entry.generation);
|
|
92
|
+
state = { path, generation: Math.max(...generations), entries, suspensions };
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function persist(current) {
|
|
97
|
+
writeJsonAtomicSync(
|
|
98
|
+
current.path,
|
|
99
|
+
{
|
|
100
|
+
version: FILE_VERSION,
|
|
101
|
+
generation: current.generation,
|
|
102
|
+
entries: [...current.entries.values()],
|
|
103
|
+
suspensions: [...current.suspensions.values()],
|
|
104
|
+
},
|
|
105
|
+
{ fsyncDir: true }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function notify(sessionId) {
|
|
110
|
+
for (const listener of [...listeners]) {
|
|
111
|
+
try {
|
|
112
|
+
listener(sessionId);
|
|
113
|
+
} catch {
|
|
114
|
+
// The mutation is already persisted; one failing listener must neither
|
|
115
|
+
// undo it nor keep the others from hearing about it.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Listener receives the changed sessionId, or null when every entry changed. */
|
|
121
|
+
export function subscribeQuotaResume(listener) {
|
|
122
|
+
listeners.add(listener);
|
|
123
|
+
return () => listeners.delete(listener);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function quotaResumeEntry(sessionId) {
|
|
127
|
+
return load().entries.get(text(sessionId)) || null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function listQuotaResumeEntries() {
|
|
131
|
+
return [...load().entries.values()];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The session snapshot's `quotaResume` value for an entry (or null). */
|
|
135
|
+
export function publicQuotaResume(entry) {
|
|
136
|
+
if (!entry) return null;
|
|
137
|
+
let snapshot = publicSnapshots.get(entry);
|
|
138
|
+
if (!snapshot) {
|
|
139
|
+
snapshot = Object.freeze({
|
|
140
|
+
state: 'waiting',
|
|
141
|
+
turnKey: entry.turnKey,
|
|
142
|
+
provider: entry.provider,
|
|
143
|
+
model: entry.model,
|
|
144
|
+
resetAt: entry.resetAt,
|
|
145
|
+
attempts: entry.attempts,
|
|
146
|
+
});
|
|
147
|
+
publicSnapshots.set(entry, snapshot);
|
|
148
|
+
}
|
|
149
|
+
return snapshot;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Queue (or replace) the session's resume under a fresh generation. */
|
|
153
|
+
export function scheduleQuotaResumeEntry({
|
|
154
|
+
sessionId,
|
|
155
|
+
turnKey,
|
|
156
|
+
provider,
|
|
157
|
+
model = '',
|
|
158
|
+
resetAt,
|
|
159
|
+
routeProvider = '',
|
|
160
|
+
routeModel = '',
|
|
161
|
+
} = {}) {
|
|
162
|
+
const current = load();
|
|
163
|
+
const entry = normalizeEntry({
|
|
164
|
+
sessionId,
|
|
165
|
+
turnKey,
|
|
166
|
+
provider,
|
|
167
|
+
model,
|
|
168
|
+
resetAt,
|
|
169
|
+
routeProvider,
|
|
170
|
+
routeModel,
|
|
171
|
+
attempts: 0,
|
|
172
|
+
generation: current.generation + 1,
|
|
173
|
+
});
|
|
174
|
+
if (!entry) throw new TypeError('quota resume needs a session, turn, provider and reset time');
|
|
175
|
+
current.generation = entry.generation;
|
|
176
|
+
current.entries.set(entry.sessionId, entry);
|
|
177
|
+
persist(current);
|
|
178
|
+
notify(entry.sessionId);
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Remove the session's entry; with `generation`, only that exact entry. */
|
|
183
|
+
export function cancelQuotaResumeEntry(sessionId, { generation = null } = {}) {
|
|
184
|
+
const current = load();
|
|
185
|
+
const entry = current.entries.get(text(sessionId));
|
|
186
|
+
if (!entry || (generation !== null && entry.generation !== generation)) return false;
|
|
187
|
+
current.entries.delete(entry.sessionId);
|
|
188
|
+
persist(current);
|
|
189
|
+
notify(entry.sessionId);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Move the exact entry to a later reading, keeping its generation. */
|
|
194
|
+
export function deferQuotaResumeEntry(sessionId, generation, { resetAt, attempts }) {
|
|
195
|
+
const current = load();
|
|
196
|
+
const entry = current.entries.get(text(sessionId));
|
|
197
|
+
if (!entry || entry.generation !== generation) return null;
|
|
198
|
+
const next = normalizeEntry({ ...entry, resetAt, attempts });
|
|
199
|
+
if (!next) throw new TypeError('quota resume deferral needs a reset time');
|
|
200
|
+
current.entries.set(next.sessionId, next);
|
|
201
|
+
persist(current);
|
|
202
|
+
notify(next.sessionId);
|
|
203
|
+
return next;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function cancelAllQuotaResumeEntries() {
|
|
207
|
+
const current = load();
|
|
208
|
+
const count = current.entries.size;
|
|
209
|
+
if (count === 0) return 0;
|
|
210
|
+
current.entries.clear();
|
|
211
|
+
persist(current);
|
|
212
|
+
notify(null);
|
|
213
|
+
return count;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Record the Goal this quota failure suspended (replaces any earlier note). */
|
|
217
|
+
export function noteQuotaSuspendedGoal(sessionId, turnKey, goalId) {
|
|
218
|
+
const suspension = normalizeSuspension({ sessionId, turnKey, goalId });
|
|
219
|
+
if (!suspension) return false;
|
|
220
|
+
const current = load();
|
|
221
|
+
current.suspensions.set(suspension.sessionId, suspension);
|
|
222
|
+
persist(current);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** The Goal the failure `turnKey` suspended, or '' when it suspended none. */
|
|
227
|
+
export function quotaSuspendedGoal(sessionId, turnKey) {
|
|
228
|
+
const suspension = load().suspensions.get(text(sessionId));
|
|
229
|
+
return suspension && suspension.turnKey === text(turnKey) ? suspension.goalId : '';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function forgetQuotaSuspendedGoal(sessionId) {
|
|
233
|
+
const current = load();
|
|
234
|
+
if (!current.suspensions.delete(text(sessionId))) return false;
|
|
235
|
+
persist(current);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Global setting, default on. */
|
|
240
|
+
export function quotaAutoResumeEnabled() {
|
|
241
|
+
return readSection(SETTING_SECTION)?.autoResume !== false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Persist the setting; turning it off cancels every pending resume. */
|
|
245
|
+
export function setQuotaAutoResumeEnabled(enabled) {
|
|
246
|
+
const next = enabled !== false;
|
|
247
|
+
updateSection(SETTING_SECTION, (section) => ({ ...(section || {}), autoResume: next }));
|
|
248
|
+
if (!next) cancelAllQuotaResumeEntries();
|
|
249
|
+
return next;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Drop the in-memory copy so the next read loads from disk (a daemon restart). */
|
|
253
|
+
export function reloadQuotaResumeStore() {
|
|
254
|
+
state = null;
|
|
255
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Shipped definitions live in code; the data dir should hold only copies the
|
|
2
|
+
// user actually changed. This module holds the pieces shared by the startup
|
|
3
|
+
// migration (seeds.mjs), the editors, and scripts/build-shipped-history.mjs:
|
|
4
|
+
// content normalisation + hashing (so the generator and the runtime compare
|
|
5
|
+
// exactly the same thing), the shipped-history file, and the backup mover.
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
8
|
+
import { dirname, join, relative } from 'node:path';
|
|
9
|
+
import { readMarkdownDocument } from './markdown-frontmatter.mjs';
|
|
10
|
+
|
|
11
|
+
export const SHIPPED_HISTORY_FILE = ['defaults', 'shipped-history.json'];
|
|
12
|
+
export const EMPTY_HISTORY = Object.freeze({ agents: {}, workflows: {}, outputStyles: {}, skills: {} });
|
|
13
|
+
|
|
14
|
+
// Leftovers from hand edits or editors that never belong to a definition.
|
|
15
|
+
const IGNORABLE_STRAY = /(\.(bak|orig|tmp)|~)$/i;
|
|
16
|
+
export const isIgnorableStray = (name) => IGNORABLE_STRAY.test(name);
|
|
17
|
+
|
|
18
|
+
const digest = (text) => createHash('sha256').update(text).digest('hex').slice(0, 32);
|
|
19
|
+
|
|
20
|
+
/** CRLF -> LF, trailing whitespace stripped per line and at the end. */
|
|
21
|
+
export function normalizeText(value) {
|
|
22
|
+
return (Buffer.isBuffer(value) ? value.toString('utf8') : String(value ?? ''))
|
|
23
|
+
.replace(/\r\n/g, '\n')
|
|
24
|
+
.replace(/[^\S\n]+$/gm, '')
|
|
25
|
+
.trimEnd();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const hashText = (value) => digest(normalizeText(value));
|
|
29
|
+
|
|
30
|
+
const oneLine = (value) =>
|
|
31
|
+
String(value || '')
|
|
32
|
+
.replace(/\s+/g, ' ')
|
|
33
|
+
.trim();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Editable surface of an agent/workflow pack. `read(name)` returns the text of
|
|
37
|
+
* a file in the pack or null. agent.json wins over frontmatter for
|
|
38
|
+
* name/description, matching the loaders.
|
|
39
|
+
*/
|
|
40
|
+
export function packDefinitionFrom(read, entry, id = '') {
|
|
41
|
+
let manifest = {};
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(read('agent.json') || '{}');
|
|
44
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) manifest = parsed;
|
|
45
|
+
} catch {
|
|
46
|
+
manifest = {};
|
|
47
|
+
}
|
|
48
|
+
const doc = readMarkdownDocument(read(oneLine(manifest.entry) || entry) || '');
|
|
49
|
+
const body = normalizeText(doc.body);
|
|
50
|
+
if (!body) return null;
|
|
51
|
+
// Every other loader-visible field (frontmatter flags such as permission or
|
|
52
|
+
// hidden, model/tool fields, manifest keys). An `id` equal to the directory
|
|
53
|
+
// id and the default `entry` are what the loaders assume anyway, so they are
|
|
54
|
+
// not differences.
|
|
55
|
+
const sameId = (value) =>
|
|
56
|
+
String(value ?? '')
|
|
57
|
+
.trim()
|
|
58
|
+
.toLowerCase() === String(id).toLowerCase();
|
|
59
|
+
const fields = {};
|
|
60
|
+
for (const [key, value] of Object.entries(doc.frontmatter)) {
|
|
61
|
+
if (key === 'name' || key === 'description' || (key === 'id' && sameId(value))) continue;
|
|
62
|
+
fields[`frontmatter.${key}`] = String(value).trim();
|
|
63
|
+
}
|
|
64
|
+
for (const [key, value] of Object.entries(manifest)) {
|
|
65
|
+
if (key === 'name' || key === 'description' || (key === 'id' && sameId(value))) continue;
|
|
66
|
+
if (key === 'entry' && (oneLine(value) === entry || !oneLine(value))) continue;
|
|
67
|
+
fields[`manifest.${key}`] = JSON.stringify(value);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
name: oneLine(manifest.name || doc.frontmatter.name),
|
|
71
|
+
description: oneLine(manifest.description || doc.frontmatter.description),
|
|
72
|
+
body,
|
|
73
|
+
fields,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const fieldsKey = (fields) => {
|
|
78
|
+
const source = fields || {};
|
|
79
|
+
return JSON.stringify(
|
|
80
|
+
Object.keys(source)
|
|
81
|
+
.sort()
|
|
82
|
+
.map((key) => [key, source[key]])
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** surfaceOnly: name/description/body, what the editors can express. */
|
|
87
|
+
export const hashDefinition = ({ name, description, body, fields }, { surfaceOnly = false } = {}) =>
|
|
88
|
+
digest(
|
|
89
|
+
JSON.stringify([oneLine(name), oneLine(description), normalizeText(body), surfaceOnly ? '' : fieldsKey(fields)])
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
export function packDefinitionFromDir(dir, entry, id = '') {
|
|
93
|
+
return packDefinitionFrom(
|
|
94
|
+
(name) => {
|
|
95
|
+
try {
|
|
96
|
+
return readFileSync(join(dir, name), 'utf8');
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
entry,
|
|
102
|
+
id
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function packHashFromDir(dir, entry, id = '') {
|
|
107
|
+
const definition = packDefinitionFromDir(dir, entry, id);
|
|
108
|
+
return definition ? hashDefinition(definition) : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
112
|
+
// Anything that is not valid UTF-8 text hashes byte-for-byte.
|
|
113
|
+
const isBinary = (content) => {
|
|
114
|
+
if (!Buffer.isBuffer(content)) return false;
|
|
115
|
+
if (content.includes(0)) return true;
|
|
116
|
+
try {
|
|
117
|
+
utf8.decode(content);
|
|
118
|
+
return false;
|
|
119
|
+
} catch {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* files: [relative '/' path, Buffer|string][]; stray files are ignored. Text
|
|
126
|
+
* files are normalised (CRLF/trailing space); binary files hash byte-for-byte.
|
|
127
|
+
*/
|
|
128
|
+
export function hashTree(files) {
|
|
129
|
+
const lines = files
|
|
130
|
+
.filter(([rel]) => !isIgnorableStray(rel.split('/').pop()))
|
|
131
|
+
.map(([rel, content]) => `${rel}\0${isBinary(content) ? `b${digest(content)}` : hashText(content)}`)
|
|
132
|
+
.sort();
|
|
133
|
+
return digest(lines.join('\n'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function readTreeFiles(dir, prefix = '') {
|
|
137
|
+
const files = [];
|
|
138
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
139
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
140
|
+
if (entry.isDirectory()) files.push(...readTreeFiles(join(dir, entry.name), rel));
|
|
141
|
+
else if (entry.isFile()) files.push([rel, readFileSync(join(dir, entry.name))]);
|
|
142
|
+
}
|
|
143
|
+
return files;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function treeHashFromDir(dir) {
|
|
147
|
+
try {
|
|
148
|
+
return hashTree(readTreeFiles(dir));
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function fileHash(file) {
|
|
155
|
+
try {
|
|
156
|
+
return hashText(readFileSync(file));
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function loadShippedHistory(rootDir) {
|
|
163
|
+
try {
|
|
164
|
+
return { ...EMPTY_HISTORY, ...JSON.parse(readFileSync(join(rootDir, ...SHIPPED_HISTORY_FILE), 'utf8')) };
|
|
165
|
+
} catch {
|
|
166
|
+
return EMPTY_HISTORY;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The copy must hold the entry file and nothing but editor files and ignorable strays. */
|
|
171
|
+
export function holdsOnlyEditorFiles(dir, allowed, entry) {
|
|
172
|
+
let entries;
|
|
173
|
+
try {
|
|
174
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return (
|
|
179
|
+
entries.some((e) => e.isFile() && e.name === entry) &&
|
|
180
|
+
entries.every((e) => e.isFile() && (allowed.includes(e.name) || isIgnorableStray(e.name)))
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Editor save of a pack whose definition equals the shipped one: no user copy
|
|
186
|
+
* should exist, so the item keeps following the shipped version. An existing
|
|
187
|
+
* copy (only editor files, a tombstone, strays) moves into a backup. Returns
|
|
188
|
+
* true when the save was absorbed; false means the caller writes the copy.
|
|
189
|
+
*/
|
|
190
|
+
export function absorbSaveEqualToShipped({ rootDir, dataDir, dir, id, entry, files, definition }) {
|
|
191
|
+
// The editor only expresses name/description/body; the shipped fields it
|
|
192
|
+
// cannot see keep applying because no copy is written.
|
|
193
|
+
const shipped = packDefinitionFromDir(join(rootDir, dir, id), entry, id);
|
|
194
|
+
if (
|
|
195
|
+
!shipped ||
|
|
196
|
+
hashDefinition(definition, { surfaceOnly: true }) !== hashDefinition(shipped, { surfaceOnly: true })
|
|
197
|
+
) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
const copy = join(dataDir, dir, id);
|
|
201
|
+
if (!existsSync(copy)) return true;
|
|
202
|
+
const allowed = [...files, '.deleted'];
|
|
203
|
+
const clean = readdirSync(copy, { withFileTypes: true }).every(
|
|
204
|
+
(e) => e.isFile() && (allowed.includes(e.name) || isIgnorableStray(e.name))
|
|
205
|
+
);
|
|
206
|
+
if (!clean) return false;
|
|
207
|
+
// A real existing definition must match the shipped hidden fields exactly
|
|
208
|
+
// (an empty set included); a tombstone-only state has no definition to compare.
|
|
209
|
+
const existing = packDefinitionFromDir(copy, entry, id);
|
|
210
|
+
if (existing && fieldsKey(existing.fields || {}) !== fieldsKey(shipped.fields)) return false;
|
|
211
|
+
return makeBackup(dataDir).move(copy);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Bump to re-run the history-based retirement for every install.
|
|
215
|
+
export const SEPARATION_VERSION = 1;
|
|
216
|
+
export const HISTORY_MARKER_FILE = '.shipped-history-retired';
|
|
217
|
+
|
|
218
|
+
/** Identity of the history retirement: separation version + history content. */
|
|
219
|
+
export const historyStamp = (rootDir) => `${SEPARATION_VERSION}:${digest(JSON.stringify(loadShippedHistory(rootDir)))}`;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Lazily-created `<dataDir>/backups/defaults-separation-<timestamp>` folder.
|
|
223
|
+
* Moved entries keep their data-dir-relative path; nothing is deleted.
|
|
224
|
+
*/
|
|
225
|
+
export function makeBackup(dataDir) {
|
|
226
|
+
let root = null;
|
|
227
|
+
let failures = 0;
|
|
228
|
+
// Atomic create: an existing backup folder is never reused.
|
|
229
|
+
const claimRoot = () => {
|
|
230
|
+
const base = join(dataDir, 'backups', `defaults-separation-${new Date().toISOString().replace(/[:.]/g, '-')}`);
|
|
231
|
+
mkdirSync(join(dataDir, 'backups'), { recursive: true });
|
|
232
|
+
for (let n = 1; ; n += 1) {
|
|
233
|
+
const candidate = n === 1 ? base : `${base}-${n}`;
|
|
234
|
+
try {
|
|
235
|
+
mkdirSync(candidate);
|
|
236
|
+
return candidate;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const freeDest = (wanted) => {
|
|
243
|
+
let dest = wanted;
|
|
244
|
+
for (let n = 2; existsSync(dest); n += 1) dest = `${wanted}.${n}`;
|
|
245
|
+
return dest;
|
|
246
|
+
};
|
|
247
|
+
return {
|
|
248
|
+
get root() {
|
|
249
|
+
return root;
|
|
250
|
+
},
|
|
251
|
+
get failures() {
|
|
252
|
+
return failures;
|
|
253
|
+
},
|
|
254
|
+
/** Moves `target` into the backup; returns false if it could not be moved. */
|
|
255
|
+
move(target) {
|
|
256
|
+
if (!existsSync(target)) return true;
|
|
257
|
+
try {
|
|
258
|
+
root ??= claimRoot();
|
|
259
|
+
const dest = freeDest(join(root, relative(dataDir, target)));
|
|
260
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
261
|
+
try {
|
|
262
|
+
renameSync(target, dest);
|
|
263
|
+
} catch {
|
|
264
|
+
cpSync(target, dest, { recursive: true, force: false, errorOnExist: true });
|
|
265
|
+
rmSync(target, { recursive: true, force: true });
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
} catch {
|
|
269
|
+
// best-effort; a stuck copy simply keeps shadowing until moved by hand
|
|
270
|
+
failures += 1;
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|