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
|
@@ -583,6 +583,8 @@ function lookupModelMetadata(originalId, provider, catalog, modelsDevCatalog) {
|
|
|
583
583
|
preserveBaseLimits: !nativeLimitsAuthoritative,
|
|
584
584
|
});
|
|
585
585
|
}
|
|
586
|
+
if (!meta && String(provider || '').startsWith('custom-'))
|
|
587
|
+
return customCatalogDefaults(originalId, catalog, modelsDevCatalog);
|
|
586
588
|
return meta
|
|
587
589
|
? {
|
|
588
590
|
...meta,
|
|
@@ -592,16 +594,39 @@ function lookupModelMetadata(originalId, provider, catalog, modelsDevCatalog) {
|
|
|
592
594
|
: null;
|
|
593
595
|
}
|
|
594
596
|
|
|
597
|
+
// A user-defined (`custom-*`) provider is in no catalog under its own id, so
|
|
598
|
+
// its discovered models match by bare model id (as listed, then without a
|
|
599
|
+
// `vendor/` route prefix) under the vendor the id names. Only the reasoning
|
|
600
|
+
// capabilities become defaults for the custom row (its overrides win); prices
|
|
601
|
+
// and limits describe the vendor's own API, not the custom endpoint.
|
|
602
|
+
function customCatalogDefaults(originalId, catalog, modelsDevCatalog) {
|
|
603
|
+
const listed = String(originalId || '');
|
|
604
|
+
for (const id of new Set([listed, listed.slice(listed.lastIndexOf('/') + 1)])) {
|
|
605
|
+
if (!id) continue;
|
|
606
|
+
const vendor = _relayedModelVendor(id) || _modelsDevProviderId(catalog[id]?.litellm_provider);
|
|
607
|
+
const meta = lookupModelMetadata(id, vendor, catalog, modelsDevCatalog);
|
|
608
|
+
if (!meta) continue;
|
|
609
|
+
return {
|
|
610
|
+
supportsReasoning: meta.supportsReasoning === true,
|
|
611
|
+
reasoningOptions: Array.isArray(meta.reasoningOptions) ? meta.reasoningOptions : [],
|
|
612
|
+
reasoningContentField: meta.reasoningContentField || null,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
|
|
595
618
|
function _normalize(entry) {
|
|
596
619
|
if (!entry || typeof entry !== 'object') return null;
|
|
597
620
|
// OpenAI's Priority processing (since renamed Fast mode) columns.
|
|
598
621
|
const fastPricing = litellmPricing(entry, '_priority');
|
|
622
|
+
const ultrafastPricing = litellmPricing(entry, '_ultrafast');
|
|
599
623
|
return {
|
|
600
624
|
contextWindow: entry.max_input_tokens || entry.max_tokens || null,
|
|
601
625
|
outputTokens: entry.max_output_tokens || null,
|
|
602
626
|
...litellmPricing(entry),
|
|
603
627
|
...litellmMediaPricing(entry),
|
|
604
628
|
...(PRICING_RATE_KEYS.some((key) => fastPricing[key] != null) ? { fastPricing } : {}),
|
|
629
|
+
...(PRICING_RATE_KEYS.some((key) => ultrafastPricing[key] != null) ? { ultrafastPricing } : {}),
|
|
605
630
|
...(entry.off_peak_multiplier ? { offPeakMultiplier: entry.off_peak_multiplier } : {}),
|
|
606
631
|
supportsVision: entry.supports_vision === true,
|
|
607
632
|
supportsFunctionCalling: entry.supports_function_calling === true,
|
|
@@ -25,8 +25,7 @@ export function reasoningUsage(usage) {
|
|
|
25
25
|
raw?.thoughts_token_count,
|
|
26
26
|
];
|
|
27
27
|
const reasoningTokens = values.map(tokenCount).find((value) => value !== null) ?? null;
|
|
28
|
-
|
|
29
|
-
return raw?.warmup_usage ? combineReasoningUsage(measured, raw.warmup_usage) : measured;
|
|
28
|
+
return { reasoningTokens, reasoningTokensComplete: reasoningTokens !== null };
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export function combineReasoningUsage(left, right) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { getUsageLedger, makeUsageRecord } from './usage-ledger.mjs';
|
|
3
|
-
import { withUsageContext } from './usage-context.mjs';
|
|
3
|
+
import { isNotedUsage, withUsageContext } from './usage-context.mjs';
|
|
4
4
|
import { ACCOUNT_PROVIDERS } from '../provider-accounts.mjs';
|
|
5
5
|
import { currentProviderAccountId } from '../provider-auth-binding.mjs';
|
|
6
6
|
|
|
@@ -10,6 +10,14 @@ import { currentProviderAccountId } from '../provider-auth-binding.mjs';
|
|
|
10
10
|
const recorded = new WeakSet();
|
|
11
11
|
const hasTokens = (usage) =>
|
|
12
12
|
['inputTokens', 'outputTokens', 'cachedTokens', 'cacheWriteTokens'].some((key) => Number(usage?.[key]) > 0);
|
|
13
|
+
// One rule for every reported usage object: tokens, or a provider-reported
|
|
14
|
+
// cost (a cost-only report is still a billed request).
|
|
15
|
+
const recordable = (usage) =>
|
|
16
|
+
hasTokens(usage) ||
|
|
17
|
+
(usage?.costUsd != null &&
|
|
18
|
+
usage.costUsd !== '' &&
|
|
19
|
+
Number.isFinite(Number(usage.costUsd)) &&
|
|
20
|
+
Number(usage.costUsd) >= 0);
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
23
|
* Runs at the common provider boundary, not inside optional diagnostic IO.
|
|
@@ -92,7 +100,7 @@ export async function accountProviderSend(provider, instance, send, model, opts
|
|
|
92
100
|
};
|
|
93
101
|
const saveAbandoned = async () => {
|
|
94
102
|
for (const [index, attempt] of (identity.abandonedUsage || []).entries()) {
|
|
95
|
-
if (
|
|
103
|
+
if (recordable(attempt.usage)) await save(attempt, `${requestId}:abandoned:${index}`);
|
|
96
104
|
}
|
|
97
105
|
};
|
|
98
106
|
let result;
|
|
@@ -102,8 +110,10 @@ export async function accountProviderSend(provider, instance, send, model, opts
|
|
|
102
110
|
await saveAbandoned();
|
|
103
111
|
// Only provider-reported partial usage is recordable; never invent
|
|
104
112
|
// tokens for a failed request or reinterpret an error as a success.
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
// Usage already noted as an abandoned attempt was just recorded above.
|
|
114
|
+
if (error?.usage) {
|
|
115
|
+
if (!isNotedUsage(error.usage)) await save(error);
|
|
116
|
+
} else if (recordable(error?.partialUsage) && !isNotedUsage(error.partialUsage))
|
|
107
117
|
await save({ usage: error.partialUsage, model: error.partialModel }, requestId, error);
|
|
108
118
|
throw error;
|
|
109
119
|
}
|
|
@@ -11,9 +11,19 @@ export const noteRequestServiceTier = (tier) => {
|
|
|
11
11
|
const identity = context.getStore();
|
|
12
12
|
if (identity) identity.requestServiceTier = tier || '';
|
|
13
13
|
};
|
|
14
|
+
// Usage objects already handed to a send's accounting, as given and as
|
|
15
|
+
// stored. Noting is idempotent, and an error that surfaces later carrying the
|
|
16
|
+
// same usage (an earlier attempt's error re-thrown) is not recorded again.
|
|
17
|
+
const notedUsage = new WeakSet();
|
|
18
|
+
export const isNotedUsage = (usage) => typeof usage === 'object' && usage !== null && notedUsage.has(usage);
|
|
14
19
|
// A provider-local retry abandons an attempt the provider still billed; the
|
|
15
20
|
// enclosing send records it alongside its own final usage.
|
|
16
21
|
export const noteAbandonedUsage = (usage, model) => {
|
|
17
22
|
const identity = context.getStore();
|
|
18
|
-
if (identity
|
|
23
|
+
if (!identity || !usage || notedUsage.has(usage)) return;
|
|
24
|
+
const stored = identity.normalizeAbandonedUsage?.(usage) ?? usage;
|
|
25
|
+
notedUsage.add(usage);
|
|
26
|
+
notedUsage.add(stored);
|
|
27
|
+
identity.abandonedUsage ||= [];
|
|
28
|
+
identity.abandonedUsage.push({ usage: stored, model });
|
|
19
29
|
};
|
|
@@ -86,7 +86,9 @@ function* planUsageRepair(rows, { providerOverrides = {}, price = priceUsage })
|
|
|
86
86
|
serviceTier: oldRates?.serviceTier,
|
|
87
87
|
kind,
|
|
88
88
|
ts: row.ts,
|
|
89
|
-
|
|
89
|
+
// Same rule as at ingestion: a live record keeps its exact
|
|
90
|
+
// request time (time-of-day pricing applies); imports do not.
|
|
91
|
+
historical: signature[6] !== 'live',
|
|
90
92
|
});
|
|
91
93
|
cost = priced.costUsd;
|
|
92
94
|
if (cost !== null && (!Number.isFinite(cost) || cost < 0)) throw new Error('Invalid repair price');
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* here mutates stored rows. Storage, schema and inserts stay in
|
|
15
15
|
* usage-ledger.mjs.
|
|
16
16
|
*/
|
|
17
|
-
import { isConversationUsageSource, num } from './usage-rollup.mjs';
|
|
17
|
+
import { isConversationUsageSource, num, usageRollupDayKey } from './usage-rollup.mjs';
|
|
18
18
|
import { normalizeUsageMeasurement, normalizeLegacyUsageDay } from './usage-measurement.mjs';
|
|
19
19
|
|
|
20
20
|
// A detail and its terminal summary can overlap on the SAME route.
|
|
@@ -329,6 +329,110 @@ export function readSessionUsage(db, sessionIds) {
|
|
|
329
329
|
return { ...usage, speedOutput, speedMs };
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
const DAY_KEY = (alias) => `printf('%04d-%02d-%02d',${alias}.day/10000,(${alias}.day/100)%100,${alias}.day%100)`;
|
|
333
|
+
const BUMP = (day) => `INSERT INTO day_revisions(day,rev) VALUES(${day},1) ON CONFLICT(day) DO UPDATE SET rev=rev+1;`;
|
|
334
|
+
const TRIGGER_ROWS = { INSERT: ['NEW'], DELETE: ['OLD'], UPDATE: ['OLD', 'NEW'] };
|
|
335
|
+
const triggers = (name, table, events, days) =>
|
|
336
|
+
events
|
|
337
|
+
.map(
|
|
338
|
+
(event) => `CREATE TRIGGER IF NOT EXISTS ${name}_${event.toLowerCase()} AFTER ${event} ON ${table}
|
|
339
|
+
BEGIN ${TRIGGER_ROWS[event].map((row) => BUMP(days(row))).join(' ')} END;`
|
|
340
|
+
)
|
|
341
|
+
.join('\n');
|
|
342
|
+
const ALL = ['INSERT', 'UPDATE', 'DELETE'];
|
|
343
|
+
// Rows that change what a route/session means (price repairs rewrite route
|
|
344
|
+
// signatures) can touch any day, so they bump the global revision ('').
|
|
345
|
+
const GLOBAL_BUMP = BUMP("''");
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* What a completed day's rollup is a function of is written only through
|
|
349
|
+
* these tables, so SQLite triggers (in the writer's own transaction, for
|
|
350
|
+
* every connection and process) bump that day's revision on any insert,
|
|
351
|
+
* update or delete. A route or session row changing bumps the global
|
|
352
|
+
* revision instead, invalidating every held day.
|
|
353
|
+
*/
|
|
354
|
+
export const DAY_REVISION_SCHEMA = `
|
|
355
|
+
CREATE TABLE IF NOT EXISTS day_revisions (day TEXT PRIMARY KEY, rev INTEGER NOT NULL) WITHOUT ROWID;
|
|
356
|
+
${triggers('day_rev_events', 'usage_events', ALL, DAY_KEY)}
|
|
357
|
+
${triggers('day_rev_daily', 'daily', ALL, (row) => `${row}.day`)}
|
|
358
|
+
${triggers('day_rev_sessions', 'day_sessions', ALL, (row) => `${row}.day`)}
|
|
359
|
+
${triggers('day_rev_legacy', 'legacy_days', ALL, (row) => `${row}.day`)}
|
|
360
|
+
CREATE TRIGGER IF NOT EXISTS day_rev_routes_update AFTER UPDATE ON usage_routes BEGIN ${GLOBAL_BUMP} END;
|
|
361
|
+
CREATE TRIGGER IF NOT EXISTS day_rev_routes_delete AFTER DELETE ON usage_routes BEGIN ${GLOBAL_BUMP} END;
|
|
362
|
+
CREATE TRIGGER IF NOT EXISTS day_rev_session_names_update AFTER UPDATE ON usage_sessions BEGIN ${GLOBAL_BUMP} END;
|
|
363
|
+
CREATE TRIGGER IF NOT EXISTS day_rev_session_names_delete AFTER DELETE ON usage_sessions BEGIN ${GLOBAL_BUMP} END;
|
|
364
|
+
`;
|
|
365
|
+
|
|
366
|
+
// In-window days (those with any stored rollup input) with their revisions.
|
|
367
|
+
function readDayFingerprints(db, fromDay, toDay) {
|
|
368
|
+
const global = db.prepare("SELECT rev FROM day_revisions WHERE day=''").get()?.rev ?? 0;
|
|
369
|
+
const revisions = new Map(
|
|
370
|
+
db
|
|
371
|
+
.prepare("SELECT day,rev FROM day_revisions WHERE day<>'' AND day BETWEEN ? AND ?")
|
|
372
|
+
.all(fromDay, toDay)
|
|
373
|
+
.map((row) => [row.day, row.rev])
|
|
374
|
+
);
|
|
375
|
+
const days = db
|
|
376
|
+
.prepare(
|
|
377
|
+
`SELECT day FROM daily WHERE day BETWEEN ?1 AND ?2 UNION SELECT day FROM day_sessions WHERE day BETWEEN ?1 AND ?2
|
|
378
|
+
UNION SELECT day FROM legacy_days WHERE day BETWEEN ?1 AND ?2`
|
|
379
|
+
)
|
|
380
|
+
.all(fromDay, toDay);
|
|
381
|
+
return Object.fromEntries(days.map(({ day }) => [day, `${global}:${revisions.get(day) ?? 0}`]));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const DELTA_DAY_QUERIES = 8;
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* The part of a whole-day rollup that `known` (day -> fingerprint of the
|
|
388
|
+
* result the caller already holds) cannot answer: every day that is today or
|
|
389
|
+
* later, the hourly day, and any day whose fingerprint moved. Returns every
|
|
390
|
+
* in-window fingerprint plus the recomputed days; assembleRollup() merges
|
|
391
|
+
* them with the caller's held days into exactly rollupUsage()'s answer. A
|
|
392
|
+
* rolling (`fromMs`) window has no reusable day totals and is not served here.
|
|
393
|
+
*/
|
|
394
|
+
export function rollupDelta(db, options = {}, known = {}, now = Date.now()) {
|
|
395
|
+
const { hourlyDay = null, fromDay = '0000-01-01', toDay = '9999-12-31' } = options;
|
|
396
|
+
const fingerprints = readDayFingerprints(db, fromDay, toDay);
|
|
397
|
+
const today = usageRollupDayKey(now);
|
|
398
|
+
const stale = Object.keys(fingerprints).filter(
|
|
399
|
+
(day) => day === hourlyDay || day >= today || known[day] !== fingerprints[day]
|
|
400
|
+
);
|
|
401
|
+
if (stale.length > DELTA_DAY_QUERIES) {
|
|
402
|
+
const full = rollupUsage(db, { hourlyDay, fromDay, toDay });
|
|
403
|
+
const fresh = Object.fromEntries(Object.keys(fingerprints).map((day) => [day, full.days[day] ?? null]));
|
|
404
|
+
return { fingerprints, fresh, ...(full.hourly ? { hourly: full.hourly } : {}) };
|
|
405
|
+
}
|
|
406
|
+
const fresh = {};
|
|
407
|
+
let hourly = hourlyDay ? { rows: [], unallocated: [] } : null;
|
|
408
|
+
for (const day of stale) {
|
|
409
|
+
const part = rollupUsage(db, { fromDay: day, toDay: day, hourlyDay: day === hourlyDay ? day : null });
|
|
410
|
+
fresh[day] = part.days[day] ?? null;
|
|
411
|
+
if (part.hourly) hourly = part.hourly;
|
|
412
|
+
}
|
|
413
|
+
return { fingerprints, fresh, ...(hourly ? { hourly } : {}) };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** `known` argument for rollupDelta from the caller's completed-day store. */
|
|
417
|
+
export function knownRollupDays(store) {
|
|
418
|
+
return Object.fromEntries([...store].map(([day, entry]) => [day, entry.fingerprint]));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Merge a rollupDelta() answer with the held completed days (day -> {fingerprint,value}). */
|
|
422
|
+
export function assembleRollup(store, delta, now = Date.now()) {
|
|
423
|
+
const today = usageRollupDayKey(now);
|
|
424
|
+
const days = {};
|
|
425
|
+
for (const day of Object.keys(delta.fingerprints).sort()) {
|
|
426
|
+
let value;
|
|
427
|
+
if (day in delta.fresh) {
|
|
428
|
+
value = delta.fresh[day];
|
|
429
|
+
if (day < today) store.set(day, { fingerprint: delta.fingerprints[day], value });
|
|
430
|
+
} else value = store.get(day).value;
|
|
431
|
+
if (value) days[day] = value;
|
|
432
|
+
}
|
|
433
|
+
return delta.hourly ? { days, hourly: delta.hourly } : { days };
|
|
434
|
+
}
|
|
435
|
+
|
|
332
436
|
/** Read cached amounts; group retained attribution separately for distinct sessions. */
|
|
333
437
|
export function rollupUsage(
|
|
334
438
|
db,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { DatabaseSync } from 'node:sqlite';
|
|
8
8
|
import { serveWorkerRequests } from '../worker-requests.mjs';
|
|
9
9
|
import { UsageLedger } from './usage-ledger.mjs';
|
|
10
|
-
import { readSessionUsage, rollupUsage } from './usage-ledger-rollup.mjs';
|
|
10
|
+
import { readSessionUsage, rollupDelta, rollupUsage } from './usage-ledger-rollup.mjs';
|
|
11
11
|
import { readQuotaHistory, readQuotaWindows } from './usage-ledger-quota.mjs';
|
|
12
12
|
import { applyUsageRepairIfUnchanged, selectUsageRepairCandidates } from './usage-ledger-repair.mjs';
|
|
13
13
|
import { unpricedCountAfter, unpricedPendingCounts } from './usage-pricing-refresh.mjs';
|
|
@@ -59,6 +59,7 @@ function recordBatch(ledger, rows) {
|
|
|
59
59
|
|
|
60
60
|
const operations = {
|
|
61
61
|
rollup: ({ path, options }) => read(path, (db) => rollupUsage(db, options)),
|
|
62
|
+
rollupDelta: ({ path, options, known, now }) => read(path, (db) => rollupDelta(db, options, known, now)),
|
|
62
63
|
sessionUsage: ({ path, sessionIds }) => read(path, (db) => readSessionUsage(db, sessionIds)),
|
|
63
64
|
record: ({ path, rows }) => write(path, (ledger) => recordBatch(ledger, rows)),
|
|
64
65
|
recordQuota: ({ path, samples }) => write(path, (ledger) => ledger.recordQuota(samples)),
|
|
@@ -11,7 +11,14 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
11
11
|
import { resolvePluginData } from '../plugin-paths.mjs';
|
|
12
12
|
import { usageRollupDayKey, isConversationUsageSource, num } from './usage-rollup.mjs';
|
|
13
13
|
import { priceUsage } from './cost.mjs';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
DAY_REVISION_SCHEMA,
|
|
16
|
+
assembleRollup,
|
|
17
|
+
knownRollupDays,
|
|
18
|
+
readSessionUsage,
|
|
19
|
+
rollupDelta,
|
|
20
|
+
rollupUsage,
|
|
21
|
+
} from './usage-ledger-rollup.mjs';
|
|
15
22
|
import { QUOTA_SCHEMA, readQuotaHistory, readQuotaWindows, recordQuotaSamples } from './usage-ledger-quota.mjs';
|
|
16
23
|
import { SHARE_ENV } from 'node:worker_threads';
|
|
17
24
|
import { createWorkerRequestClient } from '../worker-requests.mjs';
|
|
@@ -37,6 +44,7 @@ export function usageLedgerWorkerRunning() {
|
|
|
37
44
|
}
|
|
38
45
|
const stores = new Map();
|
|
39
46
|
const ROLLUP_CACHE_LIMIT = 8;
|
|
47
|
+
const windowShape = (options) => JSON.stringify({ ...options, fromMs: undefined, toMs: undefined });
|
|
40
48
|
const text = (value) => (typeof value === 'string' ? value.slice(0, 300) : '');
|
|
41
49
|
|
|
42
50
|
// Keep the full identifier, not a shortened hash. The tag also makes arbitrary
|
|
@@ -295,6 +303,10 @@ export class UsageLedger {
|
|
|
295
303
|
this.db.exec('PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA foreign_keys=ON;');
|
|
296
304
|
this.rollupStamp = null;
|
|
297
305
|
this.rollups = new Map();
|
|
306
|
+
// Completed days' rollup results, validated by day revision (trigger-maintained) on every use,
|
|
307
|
+
// so they survive stamp changes. Rolling-window results by query shape.
|
|
308
|
+
this.dayRollups = new Map();
|
|
309
|
+
this.windowRollups = new Map();
|
|
298
310
|
this.pendingRollups = new Map();
|
|
299
311
|
this.writeQueue = [];
|
|
300
312
|
this.writing = null;
|
|
@@ -340,6 +352,7 @@ export class UsageLedger {
|
|
|
340
352
|
);
|
|
341
353
|
CREATE TABLE IF NOT EXISTS legacy_days (day TEXT PRIMARY KEY, document TEXT NOT NULL);
|
|
342
354
|
${QUOTA_SCHEMA}
|
|
355
|
+
${DAY_REVISION_SCHEMA}
|
|
343
356
|
PRAGMA user_version=2;
|
|
344
357
|
`);
|
|
345
358
|
this.prepareStatements();
|
|
@@ -575,13 +588,52 @@ export class UsageLedger {
|
|
|
575
588
|
* shared between callers and must be treated as read-only.
|
|
576
589
|
*/
|
|
577
590
|
rollup(options = {}) {
|
|
578
|
-
this.syncRollupStamp();
|
|
591
|
+
const stamp = this.syncRollupStamp();
|
|
579
592
|
const key = JSON.stringify(options);
|
|
580
|
-
const result = this.rollups.get(key) ??
|
|
593
|
+
const result = this.rollups.get(key) ?? this.reusableWindow(options, stamp) ?? this.computeRollup(options, stamp);
|
|
581
594
|
this.rememberRollup(key, result);
|
|
582
595
|
return result;
|
|
583
596
|
}
|
|
584
597
|
|
|
598
|
+
/**
|
|
599
|
+
* A rolling window re-asked under an unchanged ledger is the same answer as
|
|
600
|
+
* long as no record sits in the part of the window that moved (aged out at
|
|
601
|
+
* the start, or newly reachable at the end): one indexed probe instead of
|
|
602
|
+
* a rebuild. Windows of the same shape are keyed without their edges.
|
|
603
|
+
*/
|
|
604
|
+
reusableWindow(options, stamp) {
|
|
605
|
+
if (options.fromMs == null || options.toMs == null) return null;
|
|
606
|
+
const held = this.windowRollups.get(windowShape(options));
|
|
607
|
+
if (!held || held.stamp !== stamp) return null;
|
|
608
|
+
const moved = this.db
|
|
609
|
+
.prepare('SELECT 1 FROM usage_events WHERE (ts>=? AND ts<?) OR (ts>? AND ts<=?) LIMIT 1')
|
|
610
|
+
.get(
|
|
611
|
+
Math.min(held.fromMs, options.fromMs),
|
|
612
|
+
Math.max(held.fromMs, options.fromMs),
|
|
613
|
+
Math.min(held.toMs, options.toMs),
|
|
614
|
+
Math.max(held.toMs, options.toMs)
|
|
615
|
+
);
|
|
616
|
+
return moved ? null : held.result;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
rememberWindow(options, stamp, result) {
|
|
620
|
+
if (options.fromMs == null || options.toMs == null) return;
|
|
621
|
+
const shape = windowShape(options);
|
|
622
|
+
this.windowRollups.delete(shape);
|
|
623
|
+
this.windowRollups.set(shape, { stamp, fromMs: options.fromMs, toMs: options.toMs, result });
|
|
624
|
+
if (this.windowRollups.size > ROLLUP_CACHE_LIMIT) this.windowRollups.delete(this.windowRollups.keys().next().value);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
computeRollup(options, stamp) {
|
|
628
|
+
if (options.fromMs != null) {
|
|
629
|
+
const result = rollupUsage(this.db, options);
|
|
630
|
+
this.rememberWindow(options, stamp, result);
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
const now = Date.now();
|
|
634
|
+
return assembleRollup(this.dayRollups, rollupDelta(this.db, options, knownRollupDays(this.dayRollups), now), now);
|
|
635
|
+
}
|
|
636
|
+
|
|
585
637
|
/**
|
|
586
638
|
* rollup() with the query run on the shared rollup worker's own read-only
|
|
587
639
|
* connection, so a changed ledger never costs the event loop the full
|
|
@@ -598,14 +650,29 @@ export class UsageLedger {
|
|
|
598
650
|
this.rememberRollup(key, cached);
|
|
599
651
|
return cached;
|
|
600
652
|
}
|
|
653
|
+
const reused = this.reusableWindow(options, stamp);
|
|
654
|
+
if (reused) {
|
|
655
|
+
this.rememberRollup(key, reused);
|
|
656
|
+
return reused;
|
|
657
|
+
}
|
|
601
658
|
let pending = this.pendingRollups.get(key);
|
|
602
659
|
if (!pending) {
|
|
603
|
-
|
|
660
|
+
const now = Date.now();
|
|
661
|
+
const request =
|
|
662
|
+
options.fromMs != null
|
|
663
|
+
? this.workerRequest('rollup', { options })
|
|
664
|
+
: this.workerRequest('rollupDelta', { options, known: knownRollupDays(this.dayRollups), now }).then((delta) =>
|
|
665
|
+
assembleRollup(this.dayRollups, delta, now)
|
|
666
|
+
);
|
|
667
|
+
pending = request
|
|
604
668
|
.then((result) => {
|
|
605
669
|
// A newer stamp cleared the cache meanwhile; never file this
|
|
606
670
|
// answer under it. The worker may have read commits newer than
|
|
607
671
|
// `stamp`, which only means the next stamp change discards it.
|
|
608
|
-
if (this.rollupStamp === stamp)
|
|
672
|
+
if (this.rollupStamp === stamp) {
|
|
673
|
+
this.rememberRollup(key, result);
|
|
674
|
+
this.rememberWindow(options, stamp, result);
|
|
675
|
+
}
|
|
609
676
|
return result;
|
|
610
677
|
})
|
|
611
678
|
.finally(() => {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// onnxruntime-node ships no macOS Intel (darwin-x64) native binding since
|
|
2
|
+
// 1.23, so the features built on it (memory embeddings, the Auto effort
|
|
3
|
+
// judge) are unavailable there; everything else runs normally.
|
|
4
|
+
export function onnxRuntimeSupported(platform = process.platform, arch = process.arch) {
|
|
5
|
+
return !(platform === 'darwin' && arch === 'x64');
|
|
6
|
+
}
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
// Orchestration is independent of workflow instructions and agent definitions.
|
|
2
2
|
export const ORCHESTRATION_MODES = Object.freeze(['none', 'focused', 'balanced', 'swarm']);
|
|
3
3
|
|
|
4
|
+
export const DEFAULT_ORCHESTRATION_MODE = 'balanced';
|
|
5
|
+
|
|
4
6
|
export function normalizeOrchestrationMode(value, fallback = 'none') {
|
|
5
7
|
return ORCHESTRATION_MODES.includes(value) ? value : fallback;
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
// Before the setting existed, Solo/Headless disabled delegation; Cowork (the
|
|
11
|
+
// old `default` pack) and custom workflows enabled it. Solo was the shipped
|
|
12
|
+
// default workflow then, so only a non-Solo choice is an explicit user choice.
|
|
13
|
+
// Used for sessions recorded without a mode and by the one-time file
|
|
14
|
+
// separation; the live config never infers a mode from the workflow.
|
|
15
|
+
export function legacyWorkflowOrchestrationMode(active) {
|
|
16
|
+
return active && active !== 'solo' && active !== 'headless' ? 'swarm' : 'none';
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
export function configuredOrchestrationMode(config) {
|
|
9
20
|
if (Object.hasOwn(config || {}, 'orchestrationMode')) {
|
|
10
|
-
return normalizeOrchestrationMode(config.orchestrationMode);
|
|
21
|
+
return normalizeOrchestrationMode(config.orchestrationMode, DEFAULT_ORCHESTRATION_MODE);
|
|
11
22
|
}
|
|
12
|
-
|
|
13
|
-
// (the old `default` pack) and custom workflows enabled it.
|
|
14
|
-
const legacy = config?.workflow?.active;
|
|
15
|
-
return legacy && legacy !== 'solo' && legacy !== 'headless' ? 'swarm' : 'none';
|
|
23
|
+
return DEFAULT_ORCHESTRATION_MODE;
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
export function normalizeWorkflowSelection(config) {
|
|
@@ -31,5 +39,5 @@ export function sessionOrchestrationMode(session) {
|
|
|
31
39
|
if (workflow?.delegatesAgents === false || (workflow?.agentsConfigured === true && workflow?.agents?.length === 0))
|
|
32
40
|
return 'none';
|
|
33
41
|
if (workflow?.delegatesAgents === true) return 'swarm';
|
|
34
|
-
return workflow ?
|
|
42
|
+
return workflow ? legacyWorkflowOrchestrationMode(workflow.id) : 'swarm';
|
|
35
43
|
}
|
|
@@ -36,11 +36,16 @@
|
|
|
36
36
|
"MIXDOG_STALL_FIRST_BYTE_ABORT_S",
|
|
37
37
|
"MIXDOG_CACHE_MESSAGES_TTL",
|
|
38
38
|
"MIXDOG_OAI_TRANSPORT",
|
|
39
|
-
"MIXDOG_OPENAI_OAUTH_WS_WARMUP",
|
|
40
39
|
"MIXDOG_OAI_WS_DUMP_DIR",
|
|
41
40
|
"MIXDOG_SESSION_TRANSCRIPT_LOG",
|
|
42
41
|
"MIXDOG_SEARCH_SERVER_BIN",
|
|
43
|
-
"MIXDOG_USAGE_LOG"
|
|
42
|
+
"MIXDOG_USAGE_LOG",
|
|
43
|
+
"MIXDOG_AUTO_EFFORT",
|
|
44
|
+
"MIXDOG_AUTO_EFFORT_STEPS",
|
|
45
|
+
"MIXDOG_EFFORT_JUDGE_DIR",
|
|
46
|
+
"MIXDOG_EFFORT_DECISIONS_LOG",
|
|
47
|
+
"MIXDOG_EFFORT_JUDGE_COLD_WAIT_MS",
|
|
48
|
+
"MIXDOG_EFFORT_JUDGE_TIMEOUT_MS"
|
|
44
49
|
],
|
|
45
50
|
"oauthProviders": {
|
|
46
51
|
"anthropic-oauth": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
12
|
-
import { basename,
|
|
12
|
+
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
13
|
+
import { withFileLockSync, writeJsonAtomicSync } from './atomic-file.mjs';
|
|
13
14
|
|
|
14
15
|
const MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
|
|
15
16
|
const PROJECTS_FILE = process.env.MIXDOG_PROJECTS_FILE
|
|
@@ -29,11 +30,19 @@ function normalizeKey(absPath) {
|
|
|
29
30
|
return process.platform === 'win32' ? text.replace(/[\\/]+$/, '').toLowerCase() : text.replace(/\/+$/, '');
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function readStore() {
|
|
33
|
+
function readStore(strict = false) {
|
|
33
34
|
try {
|
|
34
|
-
if (!existsSync(PROJECTS_FILE)) return { projects: [] };
|
|
35
|
+
if (!strict && !existsSync(PROJECTS_FILE)) return { projects: [] };
|
|
35
36
|
const raw = readFileSync(PROJECTS_FILE, 'utf8');
|
|
36
37
|
const parsed = JSON.parse(raw);
|
|
38
|
+
if (
|
|
39
|
+
strict &&
|
|
40
|
+
(!Array.isArray(parsed?.projects) ||
|
|
41
|
+
parsed.projects.some(
|
|
42
|
+
(entry) => !entry || typeof entry.path !== 'string' || !entry.path.trim() || !isAbsolute(entry.path)
|
|
43
|
+
))
|
|
44
|
+
)
|
|
45
|
+
throw new TypeError('Project registry is invalid.');
|
|
37
46
|
const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
|
|
38
47
|
return {
|
|
39
48
|
projects: projects
|
|
@@ -45,20 +54,23 @@ function readStore() {
|
|
|
45
54
|
...(Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}),
|
|
46
55
|
})),
|
|
47
56
|
};
|
|
48
|
-
} catch {
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (strict && error?.code !== 'ENOENT') throw error;
|
|
49
59
|
return { projects: [] };
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
// Serialize the entire read/modify/write across cooperating processes, not
|
|
64
|
+
// just publication. Unlike the generic JSON updater, a corrupt registry must
|
|
65
|
+
// never be repaired into an empty document. Preserve synchronous return values
|
|
66
|
+
// and no-op behavior; failed reads, lock acquisition and writes propagate.
|
|
67
|
+
function mutateStore(mutator) {
|
|
68
|
+
return withFileLockSync(`${PROJECTS_FILE}.lock`, () => {
|
|
69
|
+
const store = readStore(true);
|
|
70
|
+
const { value, changed } = mutator(store);
|
|
71
|
+
if (changed) writeJsonAtomicSync(PROJECTS_FILE, store, { lock: false });
|
|
72
|
+
return value;
|
|
73
|
+
});
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
/**
|
|
@@ -118,6 +130,13 @@ export function listProjects() {
|
|
|
118
130
|
return projects.slice().sort(compareProjects);
|
|
119
131
|
}
|
|
120
132
|
|
|
133
|
+
/** Permission checks must not mistake unreadable/corrupt storage for removal.
|
|
134
|
+
* Mutations also use strict reads; only ordinary listing remains tolerant. */
|
|
135
|
+
export function listProjectsStrict() {
|
|
136
|
+
const { projects } = readStore(true);
|
|
137
|
+
return projects.slice().sort(compareProjects);
|
|
138
|
+
}
|
|
139
|
+
|
|
121
140
|
/**
|
|
122
141
|
* Register a project by path. Derives the display name from the last path
|
|
123
142
|
* segment, dedupes by normalized absolute path, and persists. Does NOT create
|
|
@@ -126,20 +145,16 @@ export function listProjects() {
|
|
|
126
145
|
export function addProject(rawPath) {
|
|
127
146
|
const absPath = toAbsolute(rawPath);
|
|
128
147
|
if (!absPath) return null;
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
addedAt: Date.now(),
|
|
140
|
-
};
|
|
141
|
-
store.projects.push(entry);
|
|
142
|
-
writeStore(store);
|
|
148
|
+
const entry = mutateStore((store) => {
|
|
149
|
+
const key = normalizeKey(absPath);
|
|
150
|
+
const existing = store.projects.find((item) => normalizeKey(item.path) === key);
|
|
151
|
+
if (existing) return { value: existing, changed: false };
|
|
152
|
+
const added = { name: basename(absPath) || absPath, path: absPath, addedAt: Date.now() };
|
|
153
|
+
store.projects.push(added);
|
|
154
|
+
return { value: added, changed: true };
|
|
155
|
+
});
|
|
156
|
+
// Auxiliary markers stay best-effort and outside the registry's lock. A
|
|
157
|
+
// failed registration must not create a marker that suggests it succeeded.
|
|
143
158
|
ensureProjectIdMarker(absPath, entry.name);
|
|
144
159
|
return entry;
|
|
145
160
|
}
|
|
@@ -152,13 +167,14 @@ export function addProject(rawPath) {
|
|
|
152
167
|
export function touchProjectSelected(rawPath) {
|
|
153
168
|
const absPath = toAbsolute(rawPath);
|
|
154
169
|
if (!absPath) return null;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
170
|
+
const entry = mutateStore((store) => {
|
|
171
|
+
const key = normalizeKey(absPath);
|
|
172
|
+
const selected = store.projects.find((item) => normalizeKey(item.path) === key);
|
|
173
|
+
if (!selected) return { value: null, changed: false };
|
|
174
|
+
selected.lastSelectedAt = Date.now();
|
|
175
|
+
return { value: selected, changed: true };
|
|
176
|
+
});
|
|
177
|
+
if (entry) ensureProjectIdMarker(absPath, entry.name);
|
|
162
178
|
return entry;
|
|
163
179
|
}
|
|
164
180
|
|
|
@@ -166,12 +182,13 @@ export function touchProjectSelected(rawPath) {
|
|
|
166
182
|
export function removeProject(rawPath) {
|
|
167
183
|
const absPath = toAbsolute(rawPath);
|
|
168
184
|
if (!absPath) return false;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
185
|
+
return mutateStore((store) => {
|
|
186
|
+
const key = normalizeKey(absPath);
|
|
187
|
+
const next = store.projects.filter((entry) => normalizeKey(entry.path) !== key);
|
|
188
|
+
if (next.length === store.projects.length) return { value: false, changed: false };
|
|
189
|
+
store.projects = next;
|
|
190
|
+
return { value: true, changed: true };
|
|
191
|
+
});
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
/**
|
|
@@ -182,14 +199,14 @@ export function removeProject(rawPath) {
|
|
|
182
199
|
export function renameProject(rawPath, nextName) {
|
|
183
200
|
const absPath = toAbsolute(rawPath);
|
|
184
201
|
if (!absPath) return null;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
202
|
+
return mutateStore((store) => {
|
|
203
|
+
const key = normalizeKey(absPath);
|
|
204
|
+
const entry = store.projects.find((item) => normalizeKey(item.path) === key);
|
|
205
|
+
if (!entry) return { value: null, changed: false };
|
|
206
|
+
const trimmed = String(nextName || '').trim();
|
|
207
|
+
entry.name = trimmed || basename(absPath) || absPath;
|
|
208
|
+
return { value: entry, changed: true };
|
|
209
|
+
});
|
|
193
210
|
}
|
|
194
211
|
|
|
195
212
|
/** Whether a path currently exists on disk. */
|