mixdog 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/package.json +1 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +5 -0
- package/src/defaults/skills/pptx/SKILL.md +6 -6
- package/src/defaults/skills/pptx/references/review.md +6 -4
- package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
- package/src/defaults/skills/setup/references/actions.md +13 -3
- package/src/defaults/skills/visualize/SKILL.md +12 -9
- package/src/repl/slash-commands.mjs +16 -1
- package/src/rules/shared/60-verification.md +3 -1
- package/src/rules/shared/70-delivery.md +4 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
- package/src/runtime/agent/orchestrator/config.mjs +10 -4
- package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +122 -15
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +54 -11
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +104 -9
- package/src/runtime/agent/orchestrator/mcp/client.mjs +358 -140
- package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
- package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +294 -44
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +53 -18
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +447 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +86 -19
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
- package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +97 -51
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +26 -6
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
- package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +35 -24
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +268 -181
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +270 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
- package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +42 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +14 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +50 -23
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +22 -14
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +819 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +41 -3
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
- package/src/runtime/attachments/store.d.mts +3 -0
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
- package/src/runtime/channels/lib/worker-main.mjs +17 -14
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
- package/src/runtime/effort-judge/judge-client.mjs +56 -18
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +34 -24
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
- package/src/runtime/memory/lib/session-ingest.mjs +2 -2
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/atomic-file.mjs +25 -4
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/err-text.mjs +25 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/extension-scopes.mjs +5 -3
- package/src/runtime/shared/file-lock.mjs +37 -11
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +134 -131
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -52
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +17 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/plugin-manifest.mjs +18 -9
- package/src/runtime/shared/pristine-execution-contract.json +9 -2
- package/src/runtime/shared/pristine-execution.mjs +28 -4
- package/src/runtime/shared/provider-accounts.mjs +124 -20
- package/src/runtime/shared/provider-api-key.mjs +12 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/shell-display.mjs +9 -3
- package/src/runtime/shared/skill-mentions.d.mts +10 -0
- package/src/runtime/shared/skill-mentions.mjs +133 -0
- package/src/runtime/shared/skill-script-runtime.mjs +264 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +35 -2
- package/src/runtime/shared/turn-changes.mjs +447 -219
- package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
- package/src/runtime/shared/turn-snapshot.mjs +170 -48
- package/src/runtime/shared/turn-worktree-snapshot.mjs +160 -51
- package/src/runtime/shared/worker-requests.mjs +15 -2
- package/src/runtime/shared/worktrees/contract.mjs +0 -24
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/session-runtime/automation-agents.mjs +8 -2
- package/src/session-runtime/boot/apis.mjs +61 -0
- package/src/session-runtime/boot/config.mjs +5 -2
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/infrastructure.mjs +35 -18
- package/src/session-runtime/conversation-import/discovery.mjs +698 -0
- package/src/session-runtime/conversation-import/service.mjs +369 -0
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
- package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/connect.mjs +83 -16
- package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +135 -33
- package/src/session-runtime/mcp-glue.mjs +25 -2
- package/src/session-runtime/mcp-oauth-login.mjs +174 -0
- package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
- package/src/session-runtime/model-route/set-route.mjs +83 -15
- package/src/session-runtime/model-route/web-search-route.mjs +1 -1
- package/src/session-runtime/native-web-search/routes.mjs +22 -3
- package/src/session-runtime/new-session-config.mjs +3 -2
- package/src/session-runtime/notification-bus.mjs +9 -2
- package/src/session-runtime/provider-auth/accounts.mjs +33 -15
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
- package/src/session-runtime/provider-readiness.mjs +16 -10
- package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
- package/src/session-runtime/quick-web-search-models.mjs +13 -38
- package/src/session-runtime/resource-extension-sync.mjs +24 -5
- package/src/session-runtime/resource-mcp-api.mjs +198 -78
- package/src/session-runtime/resource-plugins-api.mjs +29 -41
- package/src/session-runtime/resource-scope-decorate.mjs +6 -8
- package/src/session-runtime/resource-skills-api.mjs +23 -2
- package/src/session-runtime/runtime-review-api.mjs +13 -0
- package/src/session-runtime/schedule-session-run.mjs +1 -1
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
- package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/services/channel-session-router.mjs +4 -1
- package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
- package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
- package/src/session-runtime/services/provider-admin.mjs +64 -18
- package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/session-runtime/setup-tool/executor.mjs +12 -1
- package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
- package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
- package/src/session-runtime/skills-api.mjs +7 -3
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/webhook-session-run.mjs +16 -4
- package/src/skill-script-entry.mjs +36 -0
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-stored-session-views.mjs +11 -3
- package/src/standalone/daemon-telemetry.mjs +55 -0
- package/src/standalone/daemon.mjs +44 -11
- package/src/standalone/session-protocol.mjs +12 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
- package/src/standalone/session-service/agent-tree.mjs +12 -0
- package/src/standalone/session-service/quota-resume.mjs +8 -0
- package/src/standalone/session-service/retention/busy.mjs +15 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
- package/src/standalone/session-service-api.mjs +5 -0
- package/src/standalone/session-service.mjs +1 -0
- package/src/tui/App.jsx +9 -1
- package/src/tui/app/app-view/floating-panel.jsx +9 -1
- package/src/tui/app/app-view.jsx +7 -0
- package/src/tui/app/create-app-pickers.mjs +4 -0
- package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
- package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
- package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +13 -16
- package/src/tui/app/skill-mention-palette.mjs +88 -0
- package/src/tui/app/slash-commands.mjs +7 -2
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/app/slash-palette.mjs +23 -7
- package/src/tui/app/transcript-row-estimate.mjs +11 -3
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
- package/src/tui/app/use-prompt-state.mjs +5 -0
- package/src/tui/components/Message.jsx +22 -1
- package/src/tui/components/PromptInput.jsx +21 -3
- package/src/tui/components/SlashCommandPalette.jsx +12 -3
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/TranscriptItem.jsx +9 -1
- package/src/tui/components/prompt-input/key-handler.mjs +21 -8
- package/src/tui/components/status-line/local-line.mjs +5 -2
- package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
- package/src/tui/components/user-message-skills.mjs +38 -0
- package/src/tui/dist/index.mjs +1323 -214
- package/src/tui/session/queue-helpers.mjs +7 -2
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +33 -24
- package/src/tui/session/session-api/extensions.mjs +26 -2
- package/src/tui/session/session-api/integrations.mjs +7 -0
- package/src/tui/session/session-api/lifecycle.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +12 -14
- package/src/tui/session/session-flow/drain.mjs +21 -5
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/tui-steering-persist.mjs +25 -9
- package/src/tui/session/turn.mjs +55 -15
- package/src/tui/statusline-ansi-bridge.mjs +1 -1
- package/src/ui/statusline-agents.mjs +15 -4
- package/src/ui/statusline.mjs +7 -3
- package/src/ui/streaming-markdown-heal.mjs +34 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
- package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
- package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// (runAskAgentLoop). Provider-not-available and trim failures throw here so
|
|
6
6
|
// the caller marks the session errored instead of leaving stage='connecting'.
|
|
7
7
|
import { getProvider } from '../../providers/registry.mjs';
|
|
8
|
-
import { prepareExplicitSkills } from '../explicit-skills.mjs';
|
|
8
|
+
import { notifySkillsApplied, prepareExplicitSkills, recordAppliedSkills } from '../explicit-skills.mjs';
|
|
9
9
|
import { prepareTurnEffortConfiguration } from '../../providers/effort-configuration.mjs';
|
|
10
10
|
import { resolveTurnAutoEffort, turnStepAutoEffort } from './ask-turn-auto-effort.mjs';
|
|
11
11
|
import { saveSessionAsync } from '../store.mjs';
|
|
@@ -27,16 +27,12 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
27
27
|
// Register the live session object for synchronous close snapshots.
|
|
28
28
|
runtime.session = session;
|
|
29
29
|
if (!provider) throw new Error(`Provider "${session.provider}" not available`);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
// Auto effort also judges every tool-result step of the turn when the
|
|
38
|
-
// installed judge was trained on steps (session/loop/step-auto-effort.mjs).
|
|
39
|
-
const stepAutoEffort = turnStepAutoEffort(autoEffort, effortConfiguration);
|
|
30
|
+
// The turn-level judgment runs while the rest of the turn is prepared; it
|
|
31
|
+
// reads the session as the turn found it (synchronously, on this call).
|
|
32
|
+
// The first request still carries its decision: it is awaited below,
|
|
33
|
+
// together with the user turn, before the effort configuration is stamped.
|
|
34
|
+
const autoEffortPending = resolveTurnAutoEffort({ sessionId, session, provider, input });
|
|
35
|
+
autoEffortPending.catch(() => {});
|
|
40
36
|
applyTurnContextMeta(session, provider);
|
|
41
37
|
const effectiveCwd = cwdOverride || session.cwd;
|
|
42
38
|
// A failed-turn retry resubmits the prompt the failure left unanswered;
|
|
@@ -46,25 +42,49 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
46
42
|
if (session.sessionStartMetaInjected !== true && !hasUserConversationMessage(session.messages)) {
|
|
47
43
|
refreshSessionBp3Environment(session, effectiveCwd);
|
|
48
44
|
}
|
|
45
|
+
// The prewarm only opens the transport (effort never shapes the socket), so
|
|
46
|
+
// it starts now instead of after the judgment.
|
|
49
47
|
const startupProviderPrewarm = startProviderPrewarm({
|
|
50
48
|
sessionId,
|
|
51
49
|
session,
|
|
52
50
|
provider,
|
|
53
|
-
turnEffort,
|
|
54
|
-
effortConfiguration,
|
|
51
|
+
turnEffort: session.effort || null,
|
|
52
|
+
effortConfiguration: null,
|
|
55
53
|
codexTurnId,
|
|
56
54
|
startedAtMs: startedAt,
|
|
57
55
|
});
|
|
58
|
-
const userTurn = await
|
|
56
|
+
const [autoEffort, userTurn] = await Promise.all([
|
|
57
|
+
autoEffortPending,
|
|
58
|
+
buildUserTurn({
|
|
59
|
+
session,
|
|
60
|
+
prompt: input.prompt,
|
|
61
|
+
context: input.context,
|
|
62
|
+
explicitPrefetch: input.explicitPrefetch,
|
|
63
|
+
turnSignal,
|
|
64
|
+
transcriptMeta,
|
|
65
|
+
turnPromptSource: input.promptSource,
|
|
66
|
+
effortConfiguration: null,
|
|
67
|
+
}),
|
|
68
|
+
]);
|
|
69
|
+
const turnEffort = autoEffort?.applied ? autoEffort.effort : session.effort || null;
|
|
70
|
+
const effortConfiguration = prepareTurnEffortConfiguration(
|
|
59
71
|
session,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
provider,
|
|
73
|
+
autoEffort?.applied ? autoEffort.effort : undefined
|
|
74
|
+
);
|
|
75
|
+
// Auto effort also judges every tool-result step of the turn when the
|
|
76
|
+
// installed judge was trained on steps (session/loop/step-auto-effort.mjs).
|
|
77
|
+
const stepAutoEffort = turnStepAutoEffort(autoEffort, effortConfiguration);
|
|
78
|
+
if (effortConfiguration) {
|
|
79
|
+
// Stamp the turn's effort on the user message (same meta order as when it
|
|
80
|
+
// was built with it: before the runtime-context provenance).
|
|
81
|
+
const { runtimeUserContext, ...meta } = userTurn.userTurnMessage.meta || {};
|
|
82
|
+
userTurn.userTurnMessage.meta = {
|
|
83
|
+
...meta,
|
|
84
|
+
effortConfiguration,
|
|
85
|
+
...(runtimeUserContext ? { runtimeUserContext } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
68
88
|
const { historyMessages, outgoing } = userTurn;
|
|
69
89
|
turn.userTurnContent = userTurn.userTurnMessage.content;
|
|
70
90
|
turn.outgoing = outgoing;
|
|
@@ -151,9 +171,16 @@ export async function runAskAgentLoop({
|
|
|
151
171
|
}
|
|
152
172
|
try {
|
|
153
173
|
if (!input.promptSource) {
|
|
154
|
-
await prepareExplicitSkills(input.prompt, outgoing, session, {
|
|
174
|
+
const applied = await prepareExplicitSkills(input.prompt, outgoing, session, {
|
|
175
|
+
cwd: effectiveCwd,
|
|
176
|
+
signal: turnSignal,
|
|
177
|
+
selection: input.skillSelection,
|
|
178
|
+
});
|
|
179
|
+
// The user turn message sits right after the history it was built on.
|
|
180
|
+
const userMessage = recordAppliedSkills(outgoing, outgoing[prepared.beforeCount - 1], applied);
|
|
155
181
|
session.messages = filterModelVisibleSessionMessages(outgoing);
|
|
156
182
|
checkpoint.schedule(true);
|
|
183
|
+
notifySkillsApplied(askOpts?.onSkillsApplied, applied, userMessage);
|
|
157
184
|
}
|
|
158
185
|
return await _api_call_with_interrupt(sessionId, (signal) =>
|
|
159
186
|
agentLoop(
|
|
@@ -152,6 +152,10 @@ export function buildAgentLoopOptions({
|
|
|
152
152
|
checkpoint.schedule(true);
|
|
153
153
|
relay(askOpts?.onSteerMessage, text, detail);
|
|
154
154
|
},
|
|
155
|
+
onSkillsApplied: (names, detail) => {
|
|
156
|
+
checkpoint.schedule(true);
|
|
157
|
+
relay(askOpts?.onSkillsApplied, names, detail);
|
|
158
|
+
},
|
|
155
159
|
notifyFn: optionalFn(askOpts?.notifyFn),
|
|
156
160
|
// Same projection compaction uses, so a session presents one identity on
|
|
157
161
|
// every request it makes.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { mergeNormalizedContentEntries } from '../loop/steering.mjs';
|
|
3
3
|
import { isDeliveredCompletion, logDuplicateSkip } from './delivered-completions.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { promptContentText } from './prompt-utils.mjs';
|
|
5
5
|
import { parseTaskNotification, taskNotificationId } from '../../../../shared/task-notification-envelope.mjs';
|
|
6
6
|
|
|
7
7
|
const STALE_USER_INJECTION_TTL_MS = 30 * 60 * 1000;
|
|
@@ -158,6 +158,14 @@ export function lateDeliveryText(text, entry, now = Date.now()) {
|
|
|
158
158
|
return `[late delivery: queued ${age} ago, before the current session owner started]\n${value}`;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// A non-completion entry may be queued explicitly as a task notification (a
|
|
162
|
+
// runtime/channel notice, never the user typing): the mode survives
|
|
163
|
+
// normalization and the durable spool so the drain stores it as one and it
|
|
164
|
+
// never drives explicit skill selection.
|
|
165
|
+
function explicitModeField(entry) {
|
|
166
|
+
return entry?.mode === PENDING_MODE_TASK_NOTIFICATION ? { mode: PENDING_MODE_TASK_NOTIFICATION } : {};
|
|
167
|
+
}
|
|
168
|
+
|
|
161
169
|
export function normalizePendingMessageEntry(entry) {
|
|
162
170
|
if (typeof entry === 'string') {
|
|
163
171
|
const text = entry.trim();
|
|
@@ -173,7 +181,10 @@ export function normalizePendingMessageEntry(entry) {
|
|
|
173
181
|
id: pendingMessageId(entry),
|
|
174
182
|
enqueuedAt: Number(entry.enqueuedAt) || Date.now(),
|
|
175
183
|
};
|
|
176
|
-
const entryOptions =
|
|
184
|
+
const entryOptions = {
|
|
185
|
+
...(entry.options && typeof entry.options === 'object' ? { options: entry.options } : {}),
|
|
186
|
+
...explicitModeField(entry),
|
|
187
|
+
};
|
|
177
188
|
const marker =
|
|
178
189
|
entry.notificationKind === COMPLETION_NOTIFICATION_KIND
|
|
179
190
|
? {
|
|
@@ -259,38 +270,35 @@ function normalizePersistedEntryBase(entry) {
|
|
|
259
270
|
}
|
|
260
271
|
: null;
|
|
261
272
|
}
|
|
273
|
+
const mode = explicitModeField(entry);
|
|
262
274
|
if (typeof entry.message === 'string') {
|
|
263
275
|
const message = entry.message.trim();
|
|
264
|
-
return message ? { id, message, enqueuedAt } : null;
|
|
276
|
+
return message ? { id, message, ...mode, enqueuedAt } : null;
|
|
265
277
|
}
|
|
266
278
|
if (Object.hasOwn(entry, 'content')) {
|
|
267
279
|
const normalized = normalizePendingMessageEntry(entry);
|
|
268
280
|
if (!normalized) return null;
|
|
269
281
|
if (typeof normalized.content === 'string' && !normalized.options) {
|
|
270
|
-
return { id, message: normalized.text || normalized.content, enqueuedAt };
|
|
282
|
+
return { id, message: normalized.text || normalized.content, ...mode, enqueuedAt };
|
|
271
283
|
}
|
|
272
284
|
return {
|
|
273
285
|
id,
|
|
274
286
|
content: normalized.content,
|
|
275
287
|
text: normalized.text,
|
|
276
288
|
...(normalized.options ? { options: normalized.options } : {}),
|
|
289
|
+
...mode,
|
|
277
290
|
enqueuedAt,
|
|
278
291
|
};
|
|
279
292
|
}
|
|
280
293
|
const text = pendingMessageText(entry);
|
|
281
|
-
return text ? { id, message: text, enqueuedAt } : null;
|
|
294
|
+
return text ? { id, message: text, ...mode, enqueuedAt } : null;
|
|
282
295
|
}
|
|
283
296
|
|
|
284
297
|
export function modelVisiblePendingMessages(messages) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
(message) =>
|
|
290
|
-
!isInternalRuntimeNotificationText(
|
|
291
|
-
message && typeof message === 'object' && Object.hasOwn(message, 'content') ? message.content : message
|
|
292
|
-
)
|
|
293
|
-
);
|
|
298
|
+
// No content filter: isInternalRuntimeNotificationText is a display-only
|
|
299
|
+
// predicate. Every canonical completion envelope matches it, and a drained
|
|
300
|
+
// entry the model never sees is never claimed/acked/released.
|
|
301
|
+
return (Array.isArray(messages) ? messages : []).map(pendingMessageQueueEntry).filter(Boolean);
|
|
294
302
|
}
|
|
295
303
|
|
|
296
304
|
export function _mergePendingMessageEntries(entries) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sanitizeToolPairs } from '../context-utils.mjs';
|
|
2
2
|
import { collectAssistantToolCallIds as assistantToolCallIds } from '../context-tool-pairs.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { promptContentText } from './prompt-utils.mjs';
|
|
4
4
|
import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
|
|
5
5
|
import { journalDelta } from './turn-interruption-journal.mjs';
|
|
6
6
|
|
|
@@ -72,8 +72,11 @@ function finalizeInterruptedTurn({
|
|
|
72
72
|
const index = provisionalUserTurnIndex(messages, currentUserContent);
|
|
73
73
|
return index >= 0 && index < messages.length - 1;
|
|
74
74
|
})();
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// A turn woken by a task notification is preserved by the same rule as a
|
|
76
|
+
// user prompt: once it produced output its notification is consumed (the
|
|
77
|
+
// host commits it and never requeues it), so rewinding it would erase the
|
|
78
|
+
// turn and its side effects from history.
|
|
79
|
+
const preserveResponse = responseStarted && (producedOutput || !userCancelled);
|
|
77
80
|
if (!preserveResponse) {
|
|
78
81
|
if (!userCancelled) {
|
|
79
82
|
return {
|
|
@@ -198,8 +201,11 @@ export function createTurnInterruptionTracker() {
|
|
|
198
201
|
// copies of the same first sentence). Reference agents never
|
|
199
202
|
// commit a replayed attempt's discarded stream at all; the
|
|
200
203
|
// tombstone exists only for the window between a retraction and
|
|
201
|
-
// its replacement.
|
|
202
|
-
|
|
204
|
+
// its replacement. Whitespace is not a replacement: it is not output
|
|
205
|
+
// (finalizeInterruptedTurn trims, and the host commits the prompt only
|
|
206
|
+
// on non-blank text), so until real text arrives the retracted bytes
|
|
207
|
+
// remain the only output the user saw.
|
|
208
|
+
if (state.tombstonedAssistantContent && value.trim()) {
|
|
203
209
|
state.tombstonedAssistantContent = '';
|
|
204
210
|
state.tombEpoch += 1;
|
|
205
211
|
}
|
|
@@ -212,8 +218,13 @@ export function createTurnInterruptionTracker() {
|
|
|
212
218
|
// REPLACE, never accumulate: consecutive retractions belong to
|
|
213
219
|
// successive attempts at the SAME answer, so keeping the earlier
|
|
214
220
|
// tombstone alongside the newer one is what multiplied the visible
|
|
215
|
-
// text across retries.
|
|
216
|
-
|
|
221
|
+
// text across retries. A whitespace-only retraction is not a newer
|
|
222
|
+
// attempt at the answer (recordTextDelta never let it supersede the
|
|
223
|
+
// draft), so it never replaces a meaningful tombstone.
|
|
224
|
+
const retracted = state.partialAssistantContent.slice(cutAt);
|
|
225
|
+
if (retracted.trim() || !state.tombstonedAssistantContent.trim()) {
|
|
226
|
+
state.tombstonedAssistantContent = retracted;
|
|
227
|
+
}
|
|
217
228
|
state.partialAssistantContent = state.partialAssistantContent.slice(0, cutAt);
|
|
218
229
|
state.textEpoch += 1;
|
|
219
230
|
state.tombEpoch += 1;
|
|
@@ -72,7 +72,6 @@ export {
|
|
|
72
72
|
// attach-on-resume guard) — resolved through the manager facade so the
|
|
73
73
|
// session-runtime turn API reaches it as mgr.publishSessionPresence.
|
|
74
74
|
export { publishSessionPresence, deleteSessionPresence } from './store.mjs';
|
|
75
|
-
export { isInternalRuntimeNotificationText as _isInternalRuntimeNotificationText } from './manager/prompt-utils.mjs';
|
|
76
75
|
|
|
77
76
|
// ── Usage-metrics surface — re-exported unchanged so loop.mjs / smoke scripts
|
|
78
77
|
// keep resolving these through the facade. ────────────────────────────────
|
|
@@ -408,9 +408,10 @@ function adoptCompactedTranscript(ctx, run) {
|
|
|
408
408
|
// providerState so the next send starts a fresh chain.
|
|
409
409
|
ctx.providerState = undefined;
|
|
410
410
|
ctx.providerStateCleared = true;
|
|
411
|
-
// Compaction
|
|
412
|
-
//
|
|
413
|
-
//
|
|
411
|
+
// Compaction starts a new loop epoch: the iteration counter restarts at 0
|
|
412
|
+
// so iteration indices (traces, usage metrics, cross-turn dedup windows)
|
|
413
|
+
// count from the compacted transcript. The counter bounds nothing — the
|
|
414
|
+
// loop has no iteration cap.
|
|
414
415
|
ctx.iterations = 0;
|
|
415
416
|
// New loop epoch so persistIterationMetrics idempotency keys do not
|
|
416
417
|
// collide when iteration indices restart at 1 (incl. iter 1 → iter 1).
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
isRetryableWireErrorEvent,
|
|
13
13
|
isRetryableStreamErrorEvent,
|
|
14
14
|
isProviderRecoveryExhausted,
|
|
15
|
+
isProviderRecoveryTerminal,
|
|
15
16
|
isServerUnavailable,
|
|
16
17
|
isStreamCut,
|
|
17
18
|
isToolInputCut,
|
|
@@ -20,7 +21,9 @@ import {
|
|
|
20
21
|
resetStallRetryBudget,
|
|
21
22
|
resolveStallRetryBudget,
|
|
22
23
|
retryAfterMsFromError,
|
|
24
|
+
sleepWithAbort,
|
|
23
25
|
} from '../providers/retry-classifier.mjs';
|
|
26
|
+
import { DETERMINISTIC_KINDS, providerErrorKind } from '../providers/provider-error-kind.mjs';
|
|
24
27
|
import {
|
|
25
28
|
persistsConfirmedImageRejection,
|
|
26
29
|
promptHasInlineImages,
|
|
@@ -34,7 +37,6 @@ import {
|
|
|
34
37
|
isThinkingReplayRejection,
|
|
35
38
|
repairThinkingReplayInPlace,
|
|
36
39
|
} from './thinking-replay-recovery.mjs';
|
|
37
|
-
import { setTimeout as sleepMs } from 'node:timers/promises';
|
|
38
40
|
import { readStreamOutcome } from '../providers/lib/stream-outcome.mjs';
|
|
39
41
|
import { cloneProviderReplay } from '../providers/lib/provider-replay.mjs';
|
|
40
42
|
import { resolveWorkerCompactPolicy } from './loop/compact-policy.mjs';
|
|
@@ -126,10 +128,10 @@ const RECOVERY_RETRY_BACKOFF_MS = retryLadderFromEnv(
|
|
|
126
128
|
[15_000, 30_000, 60_000, 60_000, 60_000]
|
|
127
129
|
);
|
|
128
130
|
|
|
129
|
-
// A server-advised wait (Retry-After) replaces the ladder step
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
131
|
+
// A server-advised wait (Retry-After / retryAfterMs) replaces the ladder step
|
|
132
|
+
// and is honored in full, never shortened. The ladder length
|
|
133
|
+
// still bounds how many replays run, the wait stays cancellable, and the
|
|
134
|
+
// reconnecting stage reports its remaining delay (retryAt).
|
|
133
135
|
|
|
134
136
|
// Jittering every backoff by 0.9–1.1 means parallel workers that lose the
|
|
135
137
|
// same uplink do not resume in lockstep; our provider layers already jitter
|
|
@@ -138,14 +140,42 @@ const TRANSPORT_RETRY_JITTER_RATIO = 0.1;
|
|
|
138
140
|
|
|
139
141
|
/** The ladder this failure class is entitled to: an unreachable network, a
|
|
140
142
|
* provider still unavailable after its own retries, or an ordinary fault. */
|
|
141
|
-
function retryLadderFor(error) {
|
|
143
|
+
function retryLadderFor(error, state = null) {
|
|
142
144
|
if (isConnectFailure(error)) return CONNECTION_RETRY_BACKOFF_MS;
|
|
143
|
-
if (isProviderRecoveryExhausted(error) && isServerUnavailable(error))
|
|
145
|
+
if (isProviderRecoveryExhausted(error) && isServerUnavailable(error) && recoveryCyclesEnabled(state)) {
|
|
146
|
+
return RECOVERY_RETRY_BACKOFF_MS;
|
|
147
|
+
}
|
|
144
148
|
return TRANSPORT_RETRY_BACKOFF_MS;
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
// retryPolicy.recoveryCycles:false — a provider that already ran its own full
|
|
152
|
+
// ladder surfaces its spent failure instead of parking on
|
|
153
|
+
// the 15/30/60/60/60s recovery cycles.
|
|
154
|
+
function recoveryCyclesEnabled(state) {
|
|
155
|
+
return loopRetryPolicy(state).recoveryCycles !== false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Per-provider loop retry policy, read off the provider object
|
|
159
|
+
// (`provider.retryPolicy`), default = the bounded ladders:
|
|
160
|
+
// unboundedConnectionRetry — a connect failure keeps waiting on the
|
|
161
|
+
// 5/10/20/40/60s… ladder (last step repeating) until the network returns;
|
|
162
|
+
// still cancellable, with "waiting for network" progress.
|
|
163
|
+
// contentFilterRetryable — a content_filter refusal is replayed
|
|
164
|
+
// under the transport budget with a contextual guidance notice.
|
|
165
|
+
// inBandInvalidRequestRetryable — an in-band stream error whose only
|
|
166
|
+
// deterministic evidence is its invalid_request envelope is replayed.
|
|
167
|
+
function loopRetryPolicy(state) {
|
|
168
|
+
const policy = state?.provider?.retryPolicy;
|
|
169
|
+
return policy && typeof policy === 'object' ? policy : {};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function waitsForNetwork(error, state) {
|
|
173
|
+
return loopRetryPolicy(state).unboundedConnectionRetry === true && isConnectFailure(error);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function retryBudgetFor(error, state = null) {
|
|
177
|
+
if (waitsForNetwork(error, state)) return Number.POSITIVE_INFINITY;
|
|
178
|
+
return retryLadderFor(error, state).length;
|
|
149
179
|
}
|
|
150
180
|
|
|
151
181
|
// A provider ladder spent on a connect failure has only shown that the network
|
|
@@ -153,15 +183,19 @@ function retryBudgetFor(error) {
|
|
|
153
183
|
// unavailable parks on the recovery cycles, but only while nothing has reached
|
|
154
184
|
// the user — a replay would otherwise re-show output or re-run a tool. Every
|
|
155
185
|
// other exhausted failure surfaces as-is.
|
|
156
|
-
|
|
186
|
+
// A provider VERDICT that recovery is over (an advice-gated denial, the
|
|
187
|
+
// repeated-overload stop) always ends it: the loop never overrides it.
|
|
188
|
+
function providerRecoveryEnded(error, outcome, state = null) {
|
|
189
|
+
if (isProviderRecoveryTerminal(error)) return true;
|
|
157
190
|
if (!isProviderRecoveryExhausted(error) || isConnectFailure(error)) return false;
|
|
191
|
+
if (!recoveryCyclesEnabled(state)) return true;
|
|
158
192
|
return !(outcome.replaySafe === true && isServerUnavailable(error));
|
|
159
193
|
}
|
|
160
194
|
|
|
161
|
-
function transportRetryWaitMs(attemptIndex, error) {
|
|
195
|
+
function transportRetryWaitMs(attemptIndex, error, state = null) {
|
|
162
196
|
const advisedMs = retryAfterMsFromError(error);
|
|
163
|
-
if (advisedMs != null) return
|
|
164
|
-
const ladder = retryLadderFor(error);
|
|
197
|
+
if (advisedMs != null) return advisedMs;
|
|
198
|
+
const ladder = retryLadderFor(error, state);
|
|
165
199
|
const base = ladder[Math.min(Math.max(attemptIndex, 0), ladder.length - 1)];
|
|
166
200
|
return jitterDelayMs(base, TRANSPORT_RETRY_JITTER_RATIO);
|
|
167
201
|
}
|
|
@@ -258,6 +292,46 @@ function traceLoop({ sessionId, nextIteration }, kind, fields) {
|
|
|
258
292
|
// 5s/15s wait rendered as a frozen turn with no explanation, so the stage
|
|
259
293
|
// change surfaces "Reconnecting... n/max" instead of leaving the user
|
|
260
294
|
// staring at a stalled stream.
|
|
295
|
+
// Unbounded connect-failure wait (retryPolicy.unboundedConnectionRetry): no
|
|
296
|
+
// "n/max" — the turn waits until the network is back, counting down each step.
|
|
297
|
+
function emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error }) {
|
|
298
|
+
if (typeof opts?.onStageChange !== 'function') return;
|
|
299
|
+
try {
|
|
300
|
+
const wait = Math.max(0, Number(waitMs) || 0);
|
|
301
|
+
opts.onStageChange('reconnecting', {
|
|
302
|
+
attempt,
|
|
303
|
+
max: null,
|
|
304
|
+
waitMs: wait,
|
|
305
|
+
retryAt: Date.now() + wait,
|
|
306
|
+
reason: 'waiting for network',
|
|
307
|
+
waitingForNetwork: true,
|
|
308
|
+
classifier: classifier || error?.code || null,
|
|
309
|
+
message: `Reconnecting… waiting for network · attempt ${attempt}${wait > 0 ? ` in ${Math.ceil(wait / 1000)}s` : ''}`,
|
|
310
|
+
});
|
|
311
|
+
} catch {
|
|
312
|
+
/* display-only */
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// content_filter on a path that opted in (retryPolicy.contentFilterRetryable,
|
|
317
|
+
// or the provider boundary stamping `contentFilterRetryable` on the error).
|
|
318
|
+
// A provider-terminal verdict outranks the opt-in.
|
|
319
|
+
function isReplayableContentFilter(sendErr, state) {
|
|
320
|
+
if (isProviderRecoveryTerminal(sendErr)) return false;
|
|
321
|
+
const optedIn = loopRetryPolicy(state).contentFilterRetryable === true || sendErr?.contentFilterRetryable === true;
|
|
322
|
+
return optedIn && providerErrorKind(sendErr).code === 'content_filter';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// The guidance travels as a contextual runtime fragment: the
|
|
326
|
+
// [mixdog-runtime] prefix keeps it out of the visible transcript.
|
|
327
|
+
const CONTENT_FILTER_NOTICE =
|
|
328
|
+
'[mixdog-runtime] The previous response was stopped by the provider content filter before it completed. ' +
|
|
329
|
+
'Continue the task, but rephrase or avoid the content that likely triggered the filter; do not repeat it verbatim.';
|
|
330
|
+
|
|
331
|
+
function contentFilterNotice() {
|
|
332
|
+
return { role: 'user', content: CONTENT_FILTER_NOTICE, meta: { source: 'content-filter-recovery' } };
|
|
333
|
+
}
|
|
334
|
+
|
|
261
335
|
function emitLoopReconnectProgress(opts, { attempt, maxAttempts, waitMs, classifier, error }) {
|
|
262
336
|
emitProviderRetryStage(opts?.onStageChange, {
|
|
263
337
|
attempt,
|
|
@@ -353,10 +427,22 @@ async function recoverFromSendError(sendErr, state) {
|
|
|
353
427
|
}
|
|
354
428
|
// Clean transient transport failure with zero exposure: replay the send
|
|
355
429
|
// after a bounded wait instead of failing the turn.
|
|
430
|
+
if (isReplayableContentFilter(sendErr, state)) {
|
|
431
|
+
if (transportRetriesUsed < TRANSPORT_RETRY_MAX && (outcome.replaySafe === true || (await retract()))) {
|
|
432
|
+
return scheduleTransportReplay(sendErr, state, {
|
|
433
|
+
budget: TRANSPORT_RETRY_MAX,
|
|
434
|
+
note: `[loop] content filter stopped the response (${loopTag(state)}); replaying with guidance`,
|
|
435
|
+
trace: { kind: 'content_filter_retry' },
|
|
436
|
+
classifier: 'content_filter',
|
|
437
|
+
notice: contentFilterNotice(),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
throw sendErr;
|
|
441
|
+
}
|
|
356
442
|
if (
|
|
357
|
-
transportRetriesUsed < retryBudgetFor(sendErr) &&
|
|
358
|
-
!providerRecoveryEnded(sendErr, outcome) &&
|
|
359
|
-
(await transportReplayPermitted(sendErr, outcome, retract))
|
|
443
|
+
transportRetriesUsed < retryBudgetFor(sendErr, state) &&
|
|
444
|
+
!providerRecoveryEnded(sendErr, outcome, state) &&
|
|
445
|
+
(await transportReplayPermitted(sendErr, outcome, retract, state))
|
|
360
446
|
) {
|
|
361
447
|
return retryTransport(sendErr, state);
|
|
362
448
|
}
|
|
@@ -373,7 +459,7 @@ async function recoverFromSendError(sendErr, state) {
|
|
|
373
459
|
) {
|
|
374
460
|
return repairThinkingReplay(recoveryMessages, state);
|
|
375
461
|
}
|
|
376
|
-
//
|
|
462
|
+
// Image-strip retry: drop inline images and replay once. Safe
|
|
377
463
|
// only when no tool was dispatched. Text exposure must be retracted first;
|
|
378
464
|
// reasoning-only high-effort streams can retry without a text reset.
|
|
379
465
|
if (
|
|
@@ -464,7 +550,7 @@ function isExposedNoToolStall(sendErr, outcome) {
|
|
|
464
550
|
// 'transient' and never fired).
|
|
465
551
|
async function recoverExposedStall(sendErr, outcome, state, retract) {
|
|
466
552
|
const { transportRetriesUsed = 0 } = state;
|
|
467
|
-
const exposedRetryBudget = retryBudgetFor(sendErr);
|
|
553
|
+
const exposedRetryBudget = retryBudgetFor(sendErr, state);
|
|
468
554
|
const len = sendErr.partialContent.length;
|
|
469
555
|
if (transportRetriesUsed < exposedRetryBudget && !isProviderRecoveryExhausted(sendErr) && (await retract())) {
|
|
470
556
|
return scheduleTransportReplay(sendErr, state, {
|
|
@@ -491,12 +577,17 @@ async function recoverExposedStall(sendErr, outcome, state, retract) {
|
|
|
491
577
|
async function scheduleTransportReplay(sendErr, state, { budget, note, trace, classifier, notice = null }) {
|
|
492
578
|
const { opts, signal, transportRetriesUsed = 0 } = state;
|
|
493
579
|
const attempt = transportRetriesUsed + 1;
|
|
494
|
-
const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr);
|
|
580
|
+
const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr, state);
|
|
495
581
|
logLoop(`${note}; transport retry ${attempt}/${budget} after ${waitMs}ms`);
|
|
496
582
|
const { kind, ...fields } = trace;
|
|
497
583
|
traceLoop(state, kind, { attempt, waitMs, ...fields });
|
|
498
|
-
|
|
499
|
-
|
|
584
|
+
if (Number.isFinite(budget)) {
|
|
585
|
+
emitLoopReconnectProgress(opts, { attempt, maxAttempts: budget, waitMs, classifier, error: sendErr });
|
|
586
|
+
} else {
|
|
587
|
+
emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error: sendErr });
|
|
588
|
+
}
|
|
589
|
+
// Chunked, abortable sleep: honored advice may exceed one timer's range.
|
|
590
|
+
await sleepWithAbort(waitMs, signal, undefined, 'transport retry wait aborted');
|
|
500
591
|
return beginFreshTransportAttempt(opts, budget, notice);
|
|
501
592
|
}
|
|
502
593
|
|
|
@@ -542,7 +633,7 @@ function continueFromShownText(sendErr, state, relayWitness) {
|
|
|
542
633
|
// budget still bounds it.
|
|
543
634
|
async function recoverToolInputCut(sendErr, outcome, state, retract) {
|
|
544
635
|
const { transportRetriesUsed = 0 } = state;
|
|
545
|
-
const budget = retryBudgetFor(sendErr);
|
|
636
|
+
const budget = retryBudgetFor(sendErr, state);
|
|
546
637
|
if (transportRetriesUsed < budget && (outcome.replaySafe === true || (await retract()))) {
|
|
547
638
|
return scheduleTransportReplay(sendErr, state, {
|
|
548
639
|
budget,
|
|
@@ -650,20 +741,55 @@ function recoverPartialToolCalls(sendErr, state, toolCalls, outcome) {
|
|
|
650
741
|
// reasoning/text exposure makes classifyError() intentionally permanent,
|
|
651
742
|
// non-terminal close, or a dropped connection (undici `terminated`, socket
|
|
652
743
|
// reset) — then retract the exposed text and replay.
|
|
653
|
-
async function transportReplayPermitted(sendErr, outcome, retract) {
|
|
654
|
-
if (
|
|
655
|
-
|
|
744
|
+
async function transportReplayPermitted(sendErr, outcome, retract, state) {
|
|
745
|
+
if (!isSemanticallyRetryable(sendErr, state)) return false;
|
|
746
|
+
// Existing replay-safety gate: an unexposed failure replays outright only
|
|
747
|
+
// when it is typed-transient (or an opted-in in-band invalid_request); a
|
|
748
|
+
// named symptom replays after retraction.
|
|
749
|
+
if (
|
|
750
|
+
outcome.replaySafe === true &&
|
|
751
|
+
(classifyError(sendErr) === 'transient' || inBandInvalidRequestReplay(sendErr, state))
|
|
752
|
+
) {
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
return await retract();
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// The ONE semantic verdict on whether this failure is worth re-issuing,
|
|
759
|
+
// independent of what was exposed (the replay-safety gate decides that
|
|
760
|
+
// afterwards): never a deterministic refusal (quota, plan, billing, policy,
|
|
761
|
+
// auth, permission, context or size) — server advice never makes one
|
|
762
|
+
// retryable — and otherwise a named transient/transport symptom.
|
|
763
|
+
function inBandInvalidRequestReplay(sendErr, state) {
|
|
764
|
+
return (
|
|
765
|
+
loopRetryPolicy(state).inBandInvalidRequestRetryable === true &&
|
|
766
|
+
providerErrorKind(sendErr).kind === 'invalid_request' &&
|
|
767
|
+
isRetryableStreamErrorEvent(sendErr)
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function isSemanticallyRetryable(sendErr, state) {
|
|
772
|
+
if (isProviderRecoveryTerminal(sendErr)) return false;
|
|
773
|
+
// Opt-in (retryPolicy.inBandInvalidRequestRetryable): a provider whose
|
|
774
|
+
// in-band stream errors carry an invalid_request envelope for server faults
|
|
775
|
+
// replays one with no other deterministic evidence; for every other
|
|
776
|
+
// provider it is a final refusal.
|
|
777
|
+
const { kind } = providerErrorKind(sendErr);
|
|
778
|
+
if (DETERMINISTIC_KINDS.has(kind) && !inBandInvalidRequestReplay(sendErr, state)) {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
return (
|
|
656
782
|
classifyError(sendErr) === 'transient' ||
|
|
657
783
|
isStreamCut(sendErr) ||
|
|
658
784
|
isRetryableWireErrorEvent(sendErr) ||
|
|
659
785
|
isRetryableStreamErrorEvent(sendErr) ||
|
|
660
|
-
isCursorTransientTransportError(sendErr)
|
|
661
|
-
|
|
786
|
+
isCursorTransientTransportError(sendErr)
|
|
787
|
+
);
|
|
662
788
|
}
|
|
663
789
|
|
|
664
790
|
function retryTransport(sendErr, state) {
|
|
665
791
|
return scheduleTransportReplay(sendErr, state, {
|
|
666
|
-
budget: retryBudgetFor(sendErr),
|
|
792
|
+
budget: retryBudgetFor(sendErr, state),
|
|
667
793
|
note:
|
|
668
794
|
`[loop] transient send failure with no dispatched tools (${loopTag(state)} code=${sendErr?.code ?? 'n/a'} ` +
|
|
669
795
|
`wsCloseCode=${sendErr?.wsCloseCode ?? 'n/a'} httpStatus=${sendErr?.httpStatus ?? sendErr?.status ?? 'n/a'})`,
|
|
@@ -700,7 +826,7 @@ function imageStripEligible(
|
|
|
700
826
|
hasImages: promptHasInlineImages(recoveryMessages),
|
|
701
827
|
alreadyStripped: imageStripUsed === true,
|
|
702
828
|
}) ||
|
|
703
|
-
// Grok
|
|
829
|
+
// Grok only: a bare stream error on a Grok
|
|
704
830
|
// upload may be the image body being rejected. Other providers' transient
|
|
705
831
|
// stream errors are not evidence about images and use normal transport retry.
|
|
706
832
|
(isGrok &&
|
|
@@ -30,6 +30,7 @@ import { _saveAsyncQueued, _saveWorkerPending } from './save-worker.mjs';
|
|
|
30
30
|
import { STORED_SESSION_UNREADABLE, _ensureLifecycleFields, _storedSessionFromFile } from './serialize.mjs';
|
|
31
31
|
import { _savePending } from './pending-saves.mjs';
|
|
32
32
|
import { isOrdinarySession } from '../store-summary-visibility.mjs';
|
|
33
|
+
import { closeSessionCatalogDb } from '../catalog-db/index.mjs';
|
|
33
34
|
|
|
34
35
|
// Disk mtime of the summary index when the in-memory cache was last refreshed
|
|
35
36
|
// from it — the cross-process staleness detector.
|
|
@@ -145,8 +146,9 @@ function scheduleSessionSummaryIndexRebuild() {
|
|
|
145
146
|
|
|
146
147
|
/**
|
|
147
148
|
* Resolves once no summary-index writer of this process is running: the
|
|
148
|
-
* background rebuild worker a cold listing (or sweep) spawned has exited
|
|
149
|
-
* every queued index mutation has landed
|
|
149
|
+
* background rebuild worker a cold listing (or sweep) spawned has exited,
|
|
150
|
+
* every queued index mutation has landed and the session catalog DB is
|
|
151
|
+
* closed. Both write the index, its lock and
|
|
150
152
|
* temp files into the data dir after the listing/sweep/save that caused them
|
|
151
153
|
* has returned, so an owner that removes or switches the data dir awaits this
|
|
152
154
|
* first.
|
|
@@ -159,6 +161,8 @@ export async function settleSessionSummaryIndex() {
|
|
|
159
161
|
await new Promise((resolve) => worker.once('exit', resolve));
|
|
160
162
|
}
|
|
161
163
|
await settleSummaryIndexWrites();
|
|
164
|
+
// The daemon's session catalog DB holds its file, WAL and lock open.
|
|
165
|
+
await closeSessionCatalogDb();
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
export function listStoredSessionSummaries(options = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MessageChannel, Worker } from 'node:worker_threads';
|
|
2
|
-
import { guardedSaveOptions as _guardedSaveOptions } from './write-guards.mjs';
|
|
2
|
+
import { guardedSaveOptions as _guardedSaveOptions, releaseSessionWriteControl } from './write-guards.mjs';
|
|
3
3
|
import { _ensureLifecycleFields } from './serialize.mjs';
|
|
4
4
|
import {
|
|
5
5
|
_attemptEvidence,
|
|
@@ -63,9 +63,12 @@ export { _hasSessionSaveBaseline } from './save-delta.mjs';
|
|
|
63
63
|
* the writes, so a write already posted for `id` still lands first; the next
|
|
64
64
|
* save for `id`, if any, simply goes out as a full snapshot.
|
|
65
65
|
*/
|
|
66
|
-
export function forgetSessionSaveBaseline(id) {
|
|
66
|
+
export function forgetSessionSaveBaseline(id, { releaseWriteControl = false } = {}) {
|
|
67
67
|
if (!id) return false;
|
|
68
68
|
const had = _dropDeltaBaseline(id);
|
|
69
|
+
// Only when the session itself is gone (close/teardown/delete): a mid-life
|
|
70
|
+
// forget (compaction) must keep the landed-epoch fence for in-flight writes.
|
|
71
|
+
if (releaseWriteControl) releaseSessionWriteControl(String(id));
|
|
69
72
|
if (_saveWorker) {
|
|
70
73
|
try {
|
|
71
74
|
_saveWorker.postMessage({ __forgetBase: String(id) });
|
|
@@ -854,7 +857,7 @@ export function purgeSessionSaveBookkeeping(id) {
|
|
|
854
857
|
}
|
|
855
858
|
}
|
|
856
859
|
// Also frees the worker-side base: a deleted id never saves again.
|
|
857
|
-
forgetSessionSaveBaseline(id);
|
|
860
|
+
forgetSessionSaveBaseline(id, { releaseWriteControl: true });
|
|
858
861
|
return purged;
|
|
859
862
|
}
|
|
860
863
|
|