mixdog 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/package.json +2 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/scripts/provider-pingpong-live.mjs +509 -0
- package/src/agents/advisor/AGENT.md +1 -1
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +6 -0
- package/src/defaults/skills/setup/SKILL.md +6 -1
- package/src/defaults/skills/setup/references/actions.md +44 -22
- package/src/defaults/skills/setup/references/surfaces.md +10 -8
- package/src/headless-exec.mjs +15 -8
- package/src/lib/keychain-cjs.cjs +7 -4
- package/src/output-styles/common.md +11 -10
- package/src/repl/slash-commands.mjs +16 -1
- package/src/rules/agent/AGENT.md +5 -5
- package/src/rules/lead/LEAD.md +5 -5
- 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/agent-trace-io.mjs +92 -61
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
- package/src/runtime/agent/orchestrator/config.mjs +5 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- 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/providers/account-pool.mjs +188 -29
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -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 +48 -37
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
- 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 +102 -45
- 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-turn/turn-blocks.mjs +20 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
- 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 +88 -21
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
- package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
- 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 +103 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
- package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -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-request-body.mjs +41 -5
- 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 +369 -112
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
- package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
- package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
- 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-channel.d.mts +1 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
- 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 +47 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
- 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 +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
- 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/output-items.mjs +23 -10
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -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 +7 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
- 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-model-capabilities.mjs +80 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
- 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-stream/event-switch.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -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/providers/transport-scope.d.mts +6 -0
- package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
- package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
- 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 +1 -97
- package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
- package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
- package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
- package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
- package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
- package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
- package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
- 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-archived-agent.mjs +34 -15
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
- package/src/runtime/computer-bridge/action-schema.mjs +44 -21
- package/src/runtime/github/tool.mjs +92 -2
- package/src/runtime/memory/index.mjs +1 -0
- package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
- package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
- package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
- package/src/runtime/memory/lib/pg/process.mjs +65 -50
- package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
- package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
- package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
- package/src/runtime/office/README.md +2 -2
- package/src/runtime/office/authoring/html-document-author.mjs +2 -0
- package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
- package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
- package/src/runtime/office/core/office-actions-read.mjs +3 -0
- package/src/runtime/office/core/office-render-preview.mjs +2 -0
- package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
- package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
- package/src/runtime/office/core/office-sessions.mjs +5 -0
- package/src/runtime/office/core/office-transactions.mjs +3 -0
- package/src/runtime/office/core/office-write-ledger.mjs +98 -0
- package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
- package/src/runtime/office/index.mjs +30 -0
- package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
- package/src/runtime/office/pdf/pdf-render.mjs +23 -7
- package/src/runtime/office/pdf/pdf-security.mjs +2 -0
- package/src/runtime/office/portable/portable-opc.mjs +4 -1
- package/src/runtime/office/quality/visual-diff.mjs +2 -0
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/bounded-file-read.mjs +13 -0
- package/src/runtime/shared/bounded-line-diff.mjs +182 -0
- package/src/runtime/shared/buffered-appender.mjs +18 -0
- package/src/runtime/shared/config.mjs +78 -18
- package/src/runtime/shared/err-text.mjs +31 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/file-permissions.d.mts +6 -1
- package/src/runtime/shared/file-permissions.mjs +54 -6
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/loopback-listener.mjs +9 -0
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/provider-api-key.mjs +2 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/review-diff.mjs +52 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
- package/src/runtime/shared/session-sidebar-host.mjs +38 -0
- package/src/runtime/shared/slow-span.mjs +114 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
- package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
- package/src/runtime/shared/tool-card-model.mjs +5 -2
- package/src/runtime/shared/tool-output-limit.mjs +4 -0
- package/src/runtime/shared/tool-primitives.mjs +13 -0
- package/src/runtime/shared/tool-result-summary.mjs +8 -1
- package/src/runtime/shared/tool-surface.mjs +18 -3
- package/src/runtime/shared/tool-work-units.mjs +40 -4
- package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
- package/src/runtime/shared/transcript-writer.mjs +10 -2
- package/src/runtime/shared/turn-changes.mjs +670 -274
- package/src/runtime/shared/turn-review-reply.mjs +8 -39
- package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
- package/src/runtime/shared/turn-snapshot.mjs +986 -690
- package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
- package/src/runtime/shared/user-data-guard.mjs +262 -49
- package/src/runtime/shared/worktrees/carry.mjs +531 -0
- package/src/runtime/shared/worktrees/contract.mjs +34 -24
- package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
- package/src/runtime/shared/worktrees/git-head.mjs +205 -0
- package/src/runtime/shared/worktrees/git.mjs +8 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
- package/src/runtime/shared/worktrees/manager.mjs +156 -8
- package/src/runtime/shared/worktrees/merge.mjs +31 -21
- package/src/runtime/shared/worktrees/paths.d.mts +6 -0
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/runtime/terminal-bridge/client.mjs +44 -11
- package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
- package/src/runtime/tidy/apply.mjs +47 -2
- package/src/runtime/tidy/run-engines.mjs +11 -5
- package/src/session-runtime/agent-tool-routing.mjs +19 -13
- package/src/session-runtime/boot/apis.mjs +19 -5
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/infrastructure.mjs +12 -1
- package/src/session-runtime/boot/session.mjs +0 -1
- package/src/session-runtime/boot/tools.mjs +4 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
- package/src/session-runtime/internal-tool-executor.mjs +6 -2
- package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
- package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
- package/src/session-runtime/lifecycle/teardown.mjs +73 -8
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/mcp-glue/connect.mjs +46 -14
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- 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 +85 -17
- 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/native-web-search-usage.mjs +51 -0
- package/src/session-runtime/native-web-search.mjs +10 -1
- package/src/session-runtime/new-session-config.mjs +4 -2
- package/src/session-runtime/provider-auth/login.mjs +4 -0
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- 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 +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/resource-scope-decorate.mjs +10 -3
- package/src/session-runtime/runtime-review-api.mjs +21 -2
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
- 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/lead-worker-index/forgotten-leads.mjs +29 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
- 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/tag-registry.mjs +15 -1
- 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-index/row-store.mjs +10 -1
- package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/services/agent-tool.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +19 -3
- package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
- package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
- package/src/session-runtime/services/provider-admin.mjs +81 -15
- package/src/session-runtime/session-transcript-purge.mjs +32 -0
- package/src/session-runtime/session-turn-api.mjs +14 -5
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/session-runtime/setup-tool/executor.mjs +36 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
- package/src/session-runtime/skills-api.mjs +3 -5
- package/src/session-runtime/tool-surface.mjs +5 -1
- package/src/session-runtime/turn/compaction-gate.mjs +25 -0
- package/src/session-runtime/turn/session-ops.mjs +7 -5
- package/src/session-runtime/turn/side-question-ops.mjs +22 -0
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +25 -9
- package/src/session-runtime/usage-stats-api.mjs +43 -11
- package/src/session-runtime/workspace/project-branches.mjs +7 -5
- package/src/session-runtime/workspace/transition-steps.mjs +129 -31
- package/src/session-runtime/workspace/transitions.mjs +190 -15
- package/src/standalone/daemon-agent-control.mjs +9 -1
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-local-session-bridge.mjs +14 -1
- package/src/standalone/daemon-owner.mjs +17 -4
- package/src/standalone/daemon-pg-stop.mjs +51 -0
- package/src/standalone/daemon-stop-budget.d.mts +2 -0
- package/src/standalone/daemon-stop-budget.mjs +18 -0
- package/src/standalone/daemon-stored-session-views.mjs +2 -0
- package/src/standalone/daemon-telemetry.mjs +74 -0
- package/src/standalone/daemon.mjs +64 -8
- package/src/standalone/desktop-service-registry.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +70 -19
- package/src/standalone/session-client.mjs +42 -0
- package/src/standalone/session-protocol.mjs +14 -0
- package/src/standalone/session-proxy/projection.mjs +41 -4
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
- package/src/standalone/session-runtime-inline-host.mjs +1 -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 +18 -3
- package/src/standalone/session-service-api.mjs +11 -1
- package/src/standalone/session-service.mjs +1 -0
- package/src/standalone/session-state-patch.d.mts +2 -1
- package/src/standalone/session-state-patch.mjs +41 -1
- package/src/tui/App.jsx +25 -0
- package/src/tui/app/app-view/floating-panel.jsx +26 -1
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/btw-controller.mjs +118 -0
- package/src/tui/app/create-app-pickers.mjs +6 -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.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
- package/src/tui/app/shell-layout/surfaces.mjs +3 -1
- package/src/tui/app/slash-commands.mjs +20 -5
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
- package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
- package/src/tui/app/use-global-key-input.mjs +5 -12
- package/src/tui/app/use-prompt-handlers.mjs +11 -0
- package/src/tui/components/BtwPanel.jsx +53 -0
- package/src/tui/components/QuestionPanel.jsx +275 -0
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/question-panel-model.mjs +156 -0
- 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/dist/index.mjs +3221 -1500
- package/src/tui/session/context-state.mjs +4 -2
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +43 -24
- package/src/tui/session/session-api/controls.mjs +2 -2
- package/src/tui/session/session-api/extensions.mjs +22 -0
- package/src/tui/session/session-api/integrations.mjs +16 -2
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +7 -2
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +11 -14
- package/src/tui/session/session-api.mjs +1 -0
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/tool-approval.mjs +10 -7
- package/src/tui/session/transcript-spill.mjs +14 -7
- package/src/tui/session/turn-stream.mjs +174 -31
- package/src/tui/session/turn.mjs +46 -16
- 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/workflows/default/WORKFLOW.md +3 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -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';
|
|
@@ -42,6 +44,7 @@ import { agentContextOverflowError } from './loop/context-overflow.mjs';
|
|
|
42
44
|
import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
|
|
43
45
|
import { isOutputLimitStopReason } from './loop/termination.mjs';
|
|
44
46
|
import { instrumentSendCallbacks } from './send-instrumentation.mjs';
|
|
47
|
+
import { rejectedToolSchema } from './tool-schema-rejection.mjs';
|
|
45
48
|
|
|
46
49
|
function normalizedIncompleteUsage(raw) {
|
|
47
50
|
if (!raw || typeof raw !== 'object') return undefined;
|
|
@@ -126,10 +129,10 @@ const RECOVERY_RETRY_BACKOFF_MS = retryLadderFromEnv(
|
|
|
126
129
|
[15_000, 30_000, 60_000, 60_000, 60_000]
|
|
127
130
|
);
|
|
128
131
|
|
|
129
|
-
// A server-advised wait (Retry-After) replaces the ladder step
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
132
|
+
// A server-advised wait (Retry-After / retryAfterMs) replaces the ladder step
|
|
133
|
+
// and is honored in full, never shortened. The ladder length
|
|
134
|
+
// still bounds how many replays run, the wait stays cancellable, and the
|
|
135
|
+
// reconnecting stage reports its remaining delay (retryAt).
|
|
133
136
|
|
|
134
137
|
// Jittering every backoff by 0.9–1.1 means parallel workers that lose the
|
|
135
138
|
// same uplink do not resume in lockstep; our provider layers already jitter
|
|
@@ -138,14 +141,42 @@ const TRANSPORT_RETRY_JITTER_RATIO = 0.1;
|
|
|
138
141
|
|
|
139
142
|
/** The ladder this failure class is entitled to: an unreachable network, a
|
|
140
143
|
* provider still unavailable after its own retries, or an ordinary fault. */
|
|
141
|
-
function retryLadderFor(error) {
|
|
144
|
+
function retryLadderFor(error, state = null) {
|
|
142
145
|
if (isConnectFailure(error)) return CONNECTION_RETRY_BACKOFF_MS;
|
|
143
|
-
if (isProviderRecoveryExhausted(error) && isServerUnavailable(error))
|
|
146
|
+
if (isProviderRecoveryExhausted(error) && isServerUnavailable(error) && recoveryCyclesEnabled(state)) {
|
|
147
|
+
return RECOVERY_RETRY_BACKOFF_MS;
|
|
148
|
+
}
|
|
144
149
|
return TRANSPORT_RETRY_BACKOFF_MS;
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
// retryPolicy.recoveryCycles:false — a provider that already ran its own full
|
|
153
|
+
// ladder surfaces its spent failure instead of parking on
|
|
154
|
+
// the 15/30/60/60/60s recovery cycles.
|
|
155
|
+
function recoveryCyclesEnabled(state) {
|
|
156
|
+
return loopRetryPolicy(state).recoveryCycles !== false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Per-provider loop retry policy, read off the provider object
|
|
160
|
+
// (`provider.retryPolicy`), default = the bounded ladders:
|
|
161
|
+
// unboundedConnectionRetry — a connect failure keeps waiting on the
|
|
162
|
+
// 5/10/20/40/60s… ladder (last step repeating) until the network returns;
|
|
163
|
+
// still cancellable, with "waiting for network" progress.
|
|
164
|
+
// contentFilterRetryable — a content_filter refusal is replayed
|
|
165
|
+
// under the transport budget with a contextual guidance notice.
|
|
166
|
+
// inBandInvalidRequestRetryable — an in-band stream error whose only
|
|
167
|
+
// deterministic evidence is its invalid_request envelope is replayed.
|
|
168
|
+
function loopRetryPolicy(state) {
|
|
169
|
+
const policy = state?.provider?.retryPolicy;
|
|
170
|
+
return policy && typeof policy === 'object' ? policy : {};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function waitsForNetwork(error, state) {
|
|
174
|
+
return loopRetryPolicy(state).unboundedConnectionRetry === true && isConnectFailure(error);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function retryBudgetFor(error, state = null) {
|
|
178
|
+
if (waitsForNetwork(error, state)) return Number.POSITIVE_INFINITY;
|
|
179
|
+
return retryLadderFor(error, state).length;
|
|
149
180
|
}
|
|
150
181
|
|
|
151
182
|
// A provider ladder spent on a connect failure has only shown that the network
|
|
@@ -153,15 +184,19 @@ function retryBudgetFor(error) {
|
|
|
153
184
|
// unavailable parks on the recovery cycles, but only while nothing has reached
|
|
154
185
|
// the user — a replay would otherwise re-show output or re-run a tool. Every
|
|
155
186
|
// other exhausted failure surfaces as-is.
|
|
156
|
-
|
|
187
|
+
// A provider VERDICT that recovery is over (an advice-gated denial, the
|
|
188
|
+
// repeated-overload stop) always ends it: the loop never overrides it.
|
|
189
|
+
function providerRecoveryEnded(error, outcome, state = null) {
|
|
190
|
+
if (isProviderRecoveryTerminal(error)) return true;
|
|
157
191
|
if (!isProviderRecoveryExhausted(error) || isConnectFailure(error)) return false;
|
|
192
|
+
if (!recoveryCyclesEnabled(state)) return true;
|
|
158
193
|
return !(outcome.replaySafe === true && isServerUnavailable(error));
|
|
159
194
|
}
|
|
160
195
|
|
|
161
|
-
function transportRetryWaitMs(attemptIndex, error) {
|
|
196
|
+
function transportRetryWaitMs(attemptIndex, error, state = null) {
|
|
162
197
|
const advisedMs = retryAfterMsFromError(error);
|
|
163
|
-
if (advisedMs != null) return
|
|
164
|
-
const ladder = retryLadderFor(error);
|
|
198
|
+
if (advisedMs != null) return advisedMs;
|
|
199
|
+
const ladder = retryLadderFor(error, state);
|
|
165
200
|
const base = ladder[Math.min(Math.max(attemptIndex, 0), ladder.length - 1)];
|
|
166
201
|
return jitterDelayMs(base, TRANSPORT_RETRY_JITTER_RATIO);
|
|
167
202
|
}
|
|
@@ -258,6 +293,46 @@ function traceLoop({ sessionId, nextIteration }, kind, fields) {
|
|
|
258
293
|
// 5s/15s wait rendered as a frozen turn with no explanation, so the stage
|
|
259
294
|
// change surfaces "Reconnecting... n/max" instead of leaving the user
|
|
260
295
|
// staring at a stalled stream.
|
|
296
|
+
// Unbounded connect-failure wait (retryPolicy.unboundedConnectionRetry): no
|
|
297
|
+
// "n/max" — the turn waits until the network is back, counting down each step.
|
|
298
|
+
function emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error }) {
|
|
299
|
+
if (typeof opts?.onStageChange !== 'function') return;
|
|
300
|
+
try {
|
|
301
|
+
const wait = Math.max(0, Number(waitMs) || 0);
|
|
302
|
+
opts.onStageChange('reconnecting', {
|
|
303
|
+
attempt,
|
|
304
|
+
max: null,
|
|
305
|
+
waitMs: wait,
|
|
306
|
+
retryAt: Date.now() + wait,
|
|
307
|
+
reason: 'waiting for network',
|
|
308
|
+
waitingForNetwork: true,
|
|
309
|
+
classifier: classifier || error?.code || null,
|
|
310
|
+
message: `Reconnecting… waiting for network · attempt ${attempt}${wait > 0 ? ` in ${Math.ceil(wait / 1000)}s` : ''}`,
|
|
311
|
+
});
|
|
312
|
+
} catch {
|
|
313
|
+
/* display-only */
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// content_filter on a path that opted in (retryPolicy.contentFilterRetryable,
|
|
318
|
+
// or the provider boundary stamping `contentFilterRetryable` on the error).
|
|
319
|
+
// A provider-terminal verdict outranks the opt-in.
|
|
320
|
+
function isReplayableContentFilter(sendErr, state) {
|
|
321
|
+
if (isProviderRecoveryTerminal(sendErr)) return false;
|
|
322
|
+
const optedIn = loopRetryPolicy(state).contentFilterRetryable === true || sendErr?.contentFilterRetryable === true;
|
|
323
|
+
return optedIn && providerErrorKind(sendErr).code === 'content_filter';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// The guidance travels as a contextual runtime fragment: the
|
|
327
|
+
// [mixdog-runtime] prefix keeps it out of the visible transcript.
|
|
328
|
+
const CONTENT_FILTER_NOTICE =
|
|
329
|
+
'[mixdog-runtime] The previous response was stopped by the provider content filter before it completed. ' +
|
|
330
|
+
'Continue the task, but rephrase or avoid the content that likely triggered the filter; do not repeat it verbatim.';
|
|
331
|
+
|
|
332
|
+
function contentFilterNotice() {
|
|
333
|
+
return { role: 'user', content: CONTENT_FILTER_NOTICE, meta: { source: 'content-filter-recovery' } };
|
|
334
|
+
}
|
|
335
|
+
|
|
261
336
|
function emitLoopReconnectProgress(opts, { attempt, maxAttempts, waitMs, classifier, error }) {
|
|
262
337
|
emitProviderRetryStage(opts?.onStageChange, {
|
|
263
338
|
attempt,
|
|
@@ -331,6 +406,7 @@ async function recoverFromSendError(sendErr, state) {
|
|
|
331
406
|
transportRetriesUsed = 0,
|
|
332
407
|
imageStripUsed = false,
|
|
333
408
|
thinkingReplayRepairUsed = false,
|
|
409
|
+
toolSchemaDropUsed = false,
|
|
334
410
|
relayWitness,
|
|
335
411
|
} = state;
|
|
336
412
|
const outcome = readStreamOutcome(sendErr, relayWitness);
|
|
@@ -353,13 +429,36 @@ async function recoverFromSendError(sendErr, state) {
|
|
|
353
429
|
}
|
|
354
430
|
// Clean transient transport failure with zero exposure: replay the send
|
|
355
431
|
// after a bounded wait instead of failing the turn.
|
|
432
|
+
if (isReplayableContentFilter(sendErr, state)) {
|
|
433
|
+
if (transportRetriesUsed < TRANSPORT_RETRY_MAX && (outcome.replaySafe === true || (await retract()))) {
|
|
434
|
+
return scheduleTransportReplay(sendErr, state, {
|
|
435
|
+
budget: TRANSPORT_RETRY_MAX,
|
|
436
|
+
note: `[loop] content filter stopped the response (${loopTag(state)}); replaying with guidance`,
|
|
437
|
+
trace: { kind: 'content_filter_retry' },
|
|
438
|
+
classifier: 'content_filter',
|
|
439
|
+
notice: contentFilterNotice(),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
throw sendErr;
|
|
443
|
+
}
|
|
356
444
|
if (
|
|
357
|
-
transportRetriesUsed < retryBudgetFor(sendErr) &&
|
|
358
|
-
!providerRecoveryEnded(sendErr, outcome) &&
|
|
359
|
-
(await transportReplayPermitted(sendErr, outcome, retract))
|
|
445
|
+
transportRetriesUsed < retryBudgetFor(sendErr, state) &&
|
|
446
|
+
!providerRecoveryEnded(sendErr, outcome, state) &&
|
|
447
|
+
(await transportReplayPermitted(sendErr, outcome, retract, state))
|
|
360
448
|
) {
|
|
361
449
|
return retryTransport(sendErr, state);
|
|
362
450
|
}
|
|
451
|
+
// A 400 naming one tool's input schema: the session drops that tool (with
|
|
452
|
+
// the notice) and the turn is replayed once without it. Nothing was
|
|
453
|
+
// generated, and the same body would fail every later turn.
|
|
454
|
+
if (toolSchemaDropUsed !== true) {
|
|
455
|
+
const exclusion = rejectedToolSchema(sendErr, state);
|
|
456
|
+
if (exclusion) {
|
|
457
|
+
logLoop(`[loop] provider rejected tool schema "${exclusion.name}" (${loopTag(state)}); dropping it and replaying once`);
|
|
458
|
+
traceLoop(state, 'tool_schema_drop', { tool: exclusion.name });
|
|
459
|
+
return { action: 'retry_tool_schema_drop', exclusion };
|
|
460
|
+
}
|
|
461
|
+
}
|
|
363
462
|
// Anthropic replay-shape refusal: the latest assistant turn carries
|
|
364
463
|
// thinking blocks the API will not accept back (a thinking run it did not
|
|
365
464
|
// produce, or an empty text block). This is deterministic — the same body
|
|
@@ -373,7 +472,7 @@ async function recoverFromSendError(sendErr, state) {
|
|
|
373
472
|
) {
|
|
374
473
|
return repairThinkingReplay(recoveryMessages, state);
|
|
375
474
|
}
|
|
376
|
-
//
|
|
475
|
+
// Image-strip retry: drop inline images and replay once. Safe
|
|
377
476
|
// only when no tool was dispatched. Text exposure must be retracted first;
|
|
378
477
|
// reasoning-only high-effort streams can retry without a text reset.
|
|
379
478
|
if (
|
|
@@ -464,7 +563,7 @@ function isExposedNoToolStall(sendErr, outcome) {
|
|
|
464
563
|
// 'transient' and never fired).
|
|
465
564
|
async function recoverExposedStall(sendErr, outcome, state, retract) {
|
|
466
565
|
const { transportRetriesUsed = 0 } = state;
|
|
467
|
-
const exposedRetryBudget = retryBudgetFor(sendErr);
|
|
566
|
+
const exposedRetryBudget = retryBudgetFor(sendErr, state);
|
|
468
567
|
const len = sendErr.partialContent.length;
|
|
469
568
|
if (transportRetriesUsed < exposedRetryBudget && !isProviderRecoveryExhausted(sendErr) && (await retract())) {
|
|
470
569
|
return scheduleTransportReplay(sendErr, state, {
|
|
@@ -491,12 +590,17 @@ async function recoverExposedStall(sendErr, outcome, state, retract) {
|
|
|
491
590
|
async function scheduleTransportReplay(sendErr, state, { budget, note, trace, classifier, notice = null }) {
|
|
492
591
|
const { opts, signal, transportRetriesUsed = 0 } = state;
|
|
493
592
|
const attempt = transportRetriesUsed + 1;
|
|
494
|
-
const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr);
|
|
593
|
+
const waitMs = transportRetryWaitMs(transportRetriesUsed, sendErr, state);
|
|
495
594
|
logLoop(`${note}; transport retry ${attempt}/${budget} after ${waitMs}ms`);
|
|
496
595
|
const { kind, ...fields } = trace;
|
|
497
596
|
traceLoop(state, kind, { attempt, waitMs, ...fields });
|
|
498
|
-
|
|
499
|
-
|
|
597
|
+
if (Number.isFinite(budget)) {
|
|
598
|
+
emitLoopReconnectProgress(opts, { attempt, maxAttempts: budget, waitMs, classifier, error: sendErr });
|
|
599
|
+
} else {
|
|
600
|
+
emitWaitingForNetwork(opts, { attempt, waitMs, classifier, error: sendErr });
|
|
601
|
+
}
|
|
602
|
+
// Chunked, abortable sleep: honored advice may exceed one timer's range.
|
|
603
|
+
await sleepWithAbort(waitMs, signal, undefined, 'transport retry wait aborted');
|
|
500
604
|
return beginFreshTransportAttempt(opts, budget, notice);
|
|
501
605
|
}
|
|
502
606
|
|
|
@@ -542,7 +646,7 @@ function continueFromShownText(sendErr, state, relayWitness) {
|
|
|
542
646
|
// budget still bounds it.
|
|
543
647
|
async function recoverToolInputCut(sendErr, outcome, state, retract) {
|
|
544
648
|
const { transportRetriesUsed = 0 } = state;
|
|
545
|
-
const budget = retryBudgetFor(sendErr);
|
|
649
|
+
const budget = retryBudgetFor(sendErr, state);
|
|
546
650
|
if (transportRetriesUsed < budget && (outcome.replaySafe === true || (await retract()))) {
|
|
547
651
|
return scheduleTransportReplay(sendErr, state, {
|
|
548
652
|
budget,
|
|
@@ -650,20 +754,55 @@ function recoverPartialToolCalls(sendErr, state, toolCalls, outcome) {
|
|
|
650
754
|
// reasoning/text exposure makes classifyError() intentionally permanent,
|
|
651
755
|
// non-terminal close, or a dropped connection (undici `terminated`, socket
|
|
652
756
|
// reset) — then retract the exposed text and replay.
|
|
653
|
-
async function transportReplayPermitted(sendErr, outcome, retract) {
|
|
654
|
-
if (
|
|
655
|
-
|
|
757
|
+
async function transportReplayPermitted(sendErr, outcome, retract, state) {
|
|
758
|
+
if (!isSemanticallyRetryable(sendErr, state)) return false;
|
|
759
|
+
// Existing replay-safety gate: an unexposed failure replays outright only
|
|
760
|
+
// when it is typed-transient (or an opted-in in-band invalid_request); a
|
|
761
|
+
// named symptom replays after retraction.
|
|
762
|
+
if (
|
|
763
|
+
outcome.replaySafe === true &&
|
|
764
|
+
(classifyError(sendErr) === 'transient' || inBandInvalidRequestReplay(sendErr, state))
|
|
765
|
+
) {
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
return await retract();
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// The ONE semantic verdict on whether this failure is worth re-issuing,
|
|
772
|
+
// independent of what was exposed (the replay-safety gate decides that
|
|
773
|
+
// afterwards): never a deterministic refusal (quota, plan, billing, policy,
|
|
774
|
+
// auth, permission, context or size) — server advice never makes one
|
|
775
|
+
// retryable — and otherwise a named transient/transport symptom.
|
|
776
|
+
function inBandInvalidRequestReplay(sendErr, state) {
|
|
777
|
+
return (
|
|
778
|
+
loopRetryPolicy(state).inBandInvalidRequestRetryable === true &&
|
|
779
|
+
providerErrorKind(sendErr).kind === 'invalid_request' &&
|
|
780
|
+
isRetryableStreamErrorEvent(sendErr)
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function isSemanticallyRetryable(sendErr, state) {
|
|
785
|
+
if (isProviderRecoveryTerminal(sendErr)) return false;
|
|
786
|
+
// Opt-in (retryPolicy.inBandInvalidRequestRetryable): a provider whose
|
|
787
|
+
// in-band stream errors carry an invalid_request envelope for server faults
|
|
788
|
+
// replays one with no other deterministic evidence; for every other
|
|
789
|
+
// provider it is a final refusal.
|
|
790
|
+
const { kind } = providerErrorKind(sendErr);
|
|
791
|
+
if (DETERMINISTIC_KINDS.has(kind) && !inBandInvalidRequestReplay(sendErr, state)) {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
return (
|
|
656
795
|
classifyError(sendErr) === 'transient' ||
|
|
657
796
|
isStreamCut(sendErr) ||
|
|
658
797
|
isRetryableWireErrorEvent(sendErr) ||
|
|
659
798
|
isRetryableStreamErrorEvent(sendErr) ||
|
|
660
|
-
isCursorTransientTransportError(sendErr)
|
|
661
|
-
|
|
799
|
+
isCursorTransientTransportError(sendErr)
|
|
800
|
+
);
|
|
662
801
|
}
|
|
663
802
|
|
|
664
803
|
function retryTransport(sendErr, state) {
|
|
665
804
|
return scheduleTransportReplay(sendErr, state, {
|
|
666
|
-
budget: retryBudgetFor(sendErr),
|
|
805
|
+
budget: retryBudgetFor(sendErr, state),
|
|
667
806
|
note:
|
|
668
807
|
`[loop] transient send failure with no dispatched tools (${loopTag(state)} code=${sendErr?.code ?? 'n/a'} ` +
|
|
669
808
|
`wsCloseCode=${sendErr?.wsCloseCode ?? 'n/a'} httpStatus=${sendErr?.httpStatus ?? sendErr?.status ?? 'n/a'})`,
|
|
@@ -700,7 +839,7 @@ function imageStripEligible(
|
|
|
700
839
|
hasImages: promptHasInlineImages(recoveryMessages),
|
|
701
840
|
alreadyStripped: imageStripUsed === true,
|
|
702
841
|
}) ||
|
|
703
|
-
// Grok
|
|
842
|
+
// Grok only: a bare stream error on a Grok
|
|
704
843
|
// upload may be the image body being rejected. Other providers' transient
|
|
705
844
|
// stream errors are not evidence about images and use normal transport retry.
|
|
706
845
|
(isGrok &&
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// side-question-store.mjs — the in-memory state behind /btw side questions:
|
|
2
|
+
// per session, the latest provider request the main loop sent (the request a
|
|
3
|
+
// side question forks) and the side question in flight. It holds live
|
|
4
|
+
// provider/tool objects, so it never reaches the session record or disk.
|
|
5
|
+
import { isAgentOwner } from '../agent-owner.mjs';
|
|
6
|
+
|
|
7
|
+
// sessionId -> { provider, model, messages, tools, opts, response }
|
|
8
|
+
const requests = new Map();
|
|
9
|
+
// sessionId -> AbortController of the side question in flight.
|
|
10
|
+
const inFlight = new Map();
|
|
11
|
+
|
|
12
|
+
// Send options that steer the live loop rather than shape the request: its
|
|
13
|
+
// abort signal, a one-shot cache-break trace tag and the per-request stall
|
|
14
|
+
// budget. A forced tool choice is dropped too (a side question must be able
|
|
15
|
+
// to answer in text); the hard-cap `none` choice is kept.
|
|
16
|
+
const LOOP_ONLY_OPTIONS = new Set(['signal', 'cacheBreakIntent', '_stallRetryBudget', 'toolChoice']);
|
|
17
|
+
|
|
18
|
+
function requestOptions(opts) {
|
|
19
|
+
const out = {};
|
|
20
|
+
for (const [key, value] of Object.entries(opts || {})) {
|
|
21
|
+
if (typeof value === 'function' || LOOP_ONLY_OPTIONS.has(key)) continue;
|
|
22
|
+
out[key] = value;
|
|
23
|
+
}
|
|
24
|
+
if (opts?.toolChoice === 'none') out.toolChoice = 'none';
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A request in the stored shape: its own message list and only the send
|
|
29
|
+
* options a fork may reuse. `response` is null until the reply settles. */
|
|
30
|
+
export function sideQuestionRequestSnapshot({ provider, model, messages, tools, opts }) {
|
|
31
|
+
return {
|
|
32
|
+
provider,
|
|
33
|
+
model,
|
|
34
|
+
messages: [...messages],
|
|
35
|
+
tools: Array.isArray(tools) ? tools : [],
|
|
36
|
+
opts: requestOptions(opts),
|
|
37
|
+
response: null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Record a provider request the main loop is sending as the session's latest
|
|
43
|
+
* one. Agent sessions record nothing (/btw is a Lead command). The handle
|
|
44
|
+
* attaches the reply once the send proceeds, or puts the previous request
|
|
45
|
+
* back when this one fails, so only a successfully sent request is kept.
|
|
46
|
+
*/
|
|
47
|
+
export function captureSideQuestionRequest({ sessionId, sessionRef, provider, model, messages, tools, opts }) {
|
|
48
|
+
if (!sessionId || !provider || isAgentOwner(sessionRef)) return null;
|
|
49
|
+
const previous = requests.get(sessionId);
|
|
50
|
+
const request = sideQuestionRequestSnapshot({ provider, model, messages, tools, opts });
|
|
51
|
+
requests.set(sessionId, request);
|
|
52
|
+
return {
|
|
53
|
+
settle(response) {
|
|
54
|
+
request.response = response || null;
|
|
55
|
+
},
|
|
56
|
+
discard() {
|
|
57
|
+
if (requests.get(sessionId) !== request) return;
|
|
58
|
+
if (previous) requests.set(sessionId, previous);
|
|
59
|
+
else requests.delete(sessionId);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function sideQuestionRequest(sessionId) {
|
|
65
|
+
return (sessionId && requests.get(sessionId)) || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Start the session's side question, aborting the one still in flight. */
|
|
69
|
+
export function beginSideQuestion(sessionId) {
|
|
70
|
+
cancelSideQuestion(sessionId);
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
inFlight.set(sessionId, controller);
|
|
73
|
+
return {
|
|
74
|
+
signal: controller.signal,
|
|
75
|
+
end() {
|
|
76
|
+
if (inFlight.get(sessionId) === controller) inFlight.delete(sessionId);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function cancelSideQuestion(sessionId) {
|
|
82
|
+
const controller = sessionId ? inFlight.get(sessionId) : null;
|
|
83
|
+
if (!controller) return false;
|
|
84
|
+
inFlight.delete(sessionId);
|
|
85
|
+
controller.abort(new Error('Side question canceled.'));
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The transcript the saved request carried is gone (clear, rewind): a later
|
|
90
|
+
* side question waits for the next request. `cancel` also aborts the side
|
|
91
|
+
* question in flight (session close / runtime unload). */
|
|
92
|
+
export function forgetSideQuestion(sessionId, { cancel = false } = {}) {
|
|
93
|
+
if (!sessionId) return;
|
|
94
|
+
requests.delete(sessionId);
|
|
95
|
+
if (cancel) cancelSideQuestion(sessionId);
|
|
96
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// side-question.mjs — /btw: a quick question answered while the main turn
|
|
2
|
+
// keeps working. The answer comes from a cache-sharing fork of the session's
|
|
3
|
+
// latest provider request (side-question-store.mjs): the same model, system
|
|
4
|
+
// prompt, tools and cache-relevant options and the same message prefix, so
|
|
5
|
+
// the provider's prompt cache serves the prefix. Only the reply that request
|
|
6
|
+
// produced and one question are appended. One provider call, no tool loop;
|
|
7
|
+
// the answer is returned to the caller and never enters the transcript.
|
|
8
|
+
import { collectAssistantToolCallIds } from './context-tool-pairs.mjs';
|
|
9
|
+
import { buildIntermediateAssistantMessage, buildToolCallAssistantMessage } from './loop/assistant-commit.mjs';
|
|
10
|
+
import { logSessionLlmUsage } from './manager/ask-turn-commit.mjs';
|
|
11
|
+
import { disposeTransportScope, forkTransportScope } from '../providers/transport-scope.mjs';
|
|
12
|
+
import { isAgentOwner } from '../agent-owner.mjs';
|
|
13
|
+
import { getProvider } from '../providers/registry.mjs';
|
|
14
|
+
import { prepareTurnEffortConfiguration } from '../providers/effort-configuration.mjs';
|
|
15
|
+
import { providerNativeToolPrefixCount } from '../runtime-core/provider-request-tools.mjs';
|
|
16
|
+
import { snapshotProviderRequestTools } from '../runtime-core/tool-catalog.mjs';
|
|
17
|
+
import { repairTranscriptBeforeProviderSend } from './loop/transcript-repair.mjs';
|
|
18
|
+
import { buildRequestSendOptions } from './manager/ask-turn-loop-options.mjs';
|
|
19
|
+
import { filterModelVisibleSessionMessages } from './manager/message-sanitize.mjs';
|
|
20
|
+
import { hasUserConversationMessage } from './manager/prompt-utils.mjs';
|
|
21
|
+
import { loadSession } from './store.mjs';
|
|
22
|
+
import { projectSyntheticUserEnvelopes } from './synthetic-user-envelope.mjs';
|
|
23
|
+
import { beginSideQuestion, sideQuestionRequest, sideQuestionRequestSnapshot } from './side-question-store.mjs';
|
|
24
|
+
|
|
25
|
+
export { cancelSideQuestion } from './side-question-store.mjs';
|
|
26
|
+
|
|
27
|
+
export const SIDE_QUESTION_NO_REQUEST = 'Send a message first, then try /btw.';
|
|
28
|
+
export const SIDE_QUESTION_TOOL_ATTEMPT =
|
|
29
|
+
'The model tried to use a tool instead of answering. A side question cannot run tools; try asking it differently.';
|
|
30
|
+
const SIDE_QUESTION_EMPTY_ANSWER = 'The model returned no answer.';
|
|
31
|
+
|
|
32
|
+
// The reply's tool calls run in the main session; the fork only needs each
|
|
33
|
+
// call answered so the request stays well-formed.
|
|
34
|
+
const SIDE_QUESTION_TOOL_STUB =
|
|
35
|
+
'This tool call belongs to the main task and is handled there; its result is not part of this side question.';
|
|
36
|
+
|
|
37
|
+
function sideQuestionPrompt(question) {
|
|
38
|
+
return [
|
|
39
|
+
'<side-question>',
|
|
40
|
+
'The user is asking you a quick side question. The main agent keeps working on the task on its own;',
|
|
41
|
+
'this exchange does not reach it and does not change that work.',
|
|
42
|
+
'Answer directly, in this one response, from what this conversation already contains.',
|
|
43
|
+
'No tools are available here: do not call any, and do not offer or promise to do anything.',
|
|
44
|
+
'If the conversation does not tell you the answer, say that you do not know.',
|
|
45
|
+
'',
|
|
46
|
+
question,
|
|
47
|
+
'</side-question>',
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The assistant turn the saved request produced, built the way the loop
|
|
52
|
+
// commits it (without the turn's one-shot transcript metadata), plus a stub
|
|
53
|
+
// result per tool call.
|
|
54
|
+
function replyMessages(response) {
|
|
55
|
+
if (!response) return [];
|
|
56
|
+
const reply = response.toolCalls?.length
|
|
57
|
+
? buildToolCallAssistantMessage(response, { calls: response.toolCalls, suppressMidTurnText: false, opts: {} })
|
|
58
|
+
: buildIntermediateAssistantMessage(response, {});
|
|
59
|
+
if (!reply) return [];
|
|
60
|
+
const stubs = collectAssistantToolCallIds(reply).map((id) => ({
|
|
61
|
+
role: 'tool',
|
|
62
|
+
toolCallId: id,
|
|
63
|
+
content: SIDE_QUESTION_TOOL_STUB,
|
|
64
|
+
}));
|
|
65
|
+
return [reply, ...stubs];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The fork of a saved request: its messages unchanged, then the reply and
|
|
70
|
+
* the question. The send options are the saved ones; the provider
|
|
71
|
+
* continuation state follows the reply when it is appended, exactly as the
|
|
72
|
+
* main loop's next request would carry it. `sharedCachePrefixCount` (the
|
|
73
|
+
* shared prefix's message count, which survives model-proxy serialization)
|
|
74
|
+
* keeps an Anthropic message breakpoint on the shared prefix, so the fork reads the
|
|
75
|
+
* saved request's cache entry and writes none for its suffix.
|
|
76
|
+
*/
|
|
77
|
+
export function buildSideQuestionRequest(request, question) {
|
|
78
|
+
const reply = replyMessages(request.response);
|
|
79
|
+
const opts = { ...request.opts };
|
|
80
|
+
if (reply.length && Object.hasOwn(request.response, 'providerState')) {
|
|
81
|
+
opts.providerState = request.response.providerState;
|
|
82
|
+
}
|
|
83
|
+
if (request.messages.length) opts.sharedCachePrefixCount = request.messages.length;
|
|
84
|
+
const messages = [
|
|
85
|
+
...request.messages,
|
|
86
|
+
...reply,
|
|
87
|
+
// A user interjection keeps its own turn (never folded into a preceding
|
|
88
|
+
// tool result), so the prefix's last block reaches the provider unchanged.
|
|
89
|
+
{ role: 'user', content: sideQuestionPrompt(question), meta: { source: 'steering' } },
|
|
90
|
+
];
|
|
91
|
+
return { messages, opts };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The request the main loop would send next for a session that has none saved
|
|
96
|
+
* (the runtime restarted, or the saved one was forgotten): the session's own
|
|
97
|
+
* provider, model, tools, send options and transcript, assembled by the same
|
|
98
|
+
* pieces the loop uses, without the loop's side effects (no compaction, no
|
|
99
|
+
* transcript repair in place, nothing written to the session). Null when the
|
|
100
|
+
* session has no conversation, no resolvable provider, or is an agent's.
|
|
101
|
+
*/
|
|
102
|
+
function nextMainRequest(sessionId) {
|
|
103
|
+
const session = sessionId ? loadSession(sessionId) : null;
|
|
104
|
+
if (!session || session.closed === true || isAgentOwner(session)) return null;
|
|
105
|
+
if (!hasUserConversationMessage(session.messages)) return null;
|
|
106
|
+
const provider = getProvider(session.provider);
|
|
107
|
+
if (!provider) return null;
|
|
108
|
+
const transcript = filterModelVisibleSessionMessages(session.messages);
|
|
109
|
+
repairTranscriptBeforeProviderSend(transcript, sessionId);
|
|
110
|
+
const opts = buildRequestSendOptions({
|
|
111
|
+
sessionId,
|
|
112
|
+
session,
|
|
113
|
+
turnEffort: session.effort || null,
|
|
114
|
+
effortConfiguration: prepareTurnEffortConfiguration(session, provider),
|
|
115
|
+
stepAutoEffort: null,
|
|
116
|
+
codexTurnId: null,
|
|
117
|
+
startedAtMs: Date.now(),
|
|
118
|
+
signal: undefined,
|
|
119
|
+
});
|
|
120
|
+
const sendTools = snapshotProviderRequestTools({
|
|
121
|
+
provider: session.provider || provider.name,
|
|
122
|
+
tools: session.tools,
|
|
123
|
+
nativeTools: opts.nativeTools,
|
|
124
|
+
messages: transcript,
|
|
125
|
+
session,
|
|
126
|
+
});
|
|
127
|
+
opts.iteration = 1;
|
|
128
|
+
opts.providerToolSnapshotAuthoritative = true;
|
|
129
|
+
opts.providerNativeToolPrefixCount = providerNativeToolPrefixCount(sendTools);
|
|
130
|
+
return sideQuestionRequestSnapshot({
|
|
131
|
+
provider,
|
|
132
|
+
model: session.model,
|
|
133
|
+
messages: projectSyntheticUserEnvelopes(transcript).messages,
|
|
134
|
+
tools: sendTools,
|
|
135
|
+
opts,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function answerText(response) {
|
|
140
|
+
if (response?.toolCalls?.length) return SIDE_QUESTION_TOOL_ATTEMPT;
|
|
141
|
+
const text = typeof response?.content === 'string' ? response.content.trim() : '';
|
|
142
|
+
return text || SIDE_QUESTION_EMPTY_ANSWER;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Answer `question` from the session's latest provider request. One side
|
|
147
|
+
* question runs per session: a new one aborts the previous, and
|
|
148
|
+
* cancelSideQuestion aborts it. A session without a saved request forks the
|
|
149
|
+
* request its next turn would send. Rejects when the session has no
|
|
150
|
+
* conversation or provider, with the provider's error, or with the cancel
|
|
151
|
+
* reason.
|
|
152
|
+
*/
|
|
153
|
+
export async function askSideQuestion(sessionId, question) {
|
|
154
|
+
const text = typeof question === 'string' ? question.trim() : '';
|
|
155
|
+
if (!text) throw new TypeError('Side question is empty.');
|
|
156
|
+
const request = sideQuestionRequest(sessionId) || nextMainRequest(sessionId);
|
|
157
|
+
if (!request) throw new Error(SIDE_QUESTION_NO_REQUEST);
|
|
158
|
+
const run = beginSideQuestion(sessionId);
|
|
159
|
+
const startedAt = Date.now();
|
|
160
|
+
// The fork keeps the session id (prompt cache routing) but runs on its own
|
|
161
|
+
// transport scope: no pooled socket, response-chain anchor, turn-state
|
|
162
|
+
// token or provider run of the main session is waited on, read or replaced.
|
|
163
|
+
const transportScope = forkTransportScope(sessionId, 'side');
|
|
164
|
+
try {
|
|
165
|
+
const { messages, opts } = buildSideQuestionRequest(request, text);
|
|
166
|
+
let response;
|
|
167
|
+
try {
|
|
168
|
+
response = await request.provider.send(messages, request.model, request.tools.length ? request.tools : undefined, {
|
|
169
|
+
...opts,
|
|
170
|
+
transportScope,
|
|
171
|
+
admissionOwner: sessionId,
|
|
172
|
+
signal: run.signal,
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw run.signal.aborted ? run.signal.reason : error;
|
|
176
|
+
}
|
|
177
|
+
if (run.signal.aborted) throw run.signal.reason;
|
|
178
|
+
const session = request.opts.session;
|
|
179
|
+
if (response?.usage && session?.id) {
|
|
180
|
+
void logSessionLlmUsage({
|
|
181
|
+
session: { ...session, model: request.model },
|
|
182
|
+
usage: response.usage,
|
|
183
|
+
startedAt,
|
|
184
|
+
mode: 'side-question',
|
|
185
|
+
}).catch(() => {});
|
|
186
|
+
}
|
|
187
|
+
return { text: answerText(response) };
|
|
188
|
+
} finally {
|
|
189
|
+
run.end();
|
|
190
|
+
disposeTransportScope(transportScope, request.provider);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -158,9 +158,23 @@ function _logSessionCloseMetric(id, existing, closedAt, reason) {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* A barrier that found no record at all — nothing on disk and no live or
|
|
163
|
+
* pending copy in memory — fenced nothing because there is nothing to fence:
|
|
164
|
+
* its pending writes were already cancelled under the commit lock. It still
|
|
165
|
+
* returns null (no generation exists), but a caller passing `options.outcome`
|
|
166
|
+
* learns that the record was legitimately gone rather than vetoed, contended
|
|
167
|
+
* or failed.
|
|
168
|
+
*/
|
|
169
|
+
function _recordAbsent(options) {
|
|
170
|
+
if (options?.outcome && typeof options.outcome === 'object') options.outcome.absent = true;
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
161
174
|
/**
|
|
162
175
|
* Atomically mark a session closed on disk with a bumped generation.
|
|
163
176
|
* Returns the new generation, or null if the session file doesn't exist.
|
|
177
|
+
* `options.outcome` (an object) gets `absent: true` when no record existed.
|
|
164
178
|
* Used by closeSession() to plant a tombstone that races against in-flight
|
|
165
179
|
* saveSession() calls.
|
|
166
180
|
*/
|
|
@@ -187,7 +201,7 @@ export function markSessionClosed(id, reason = 'manual', options = {}) {
|
|
|
187
201
|
_cancelSessionWrites(id);
|
|
188
202
|
_uncacheSessionSummary(id);
|
|
189
203
|
const existing = loadSession(id);
|
|
190
|
-
if (!existing) return
|
|
204
|
+
if (!existing) return _recordAbsent(options);
|
|
191
205
|
// Re-close idempotence: a session that is ALREADY tombstoned keeps its
|
|
192
206
|
// ORIGINAL close time (updatedAt) and generation. The old code refreshed
|
|
193
207
|
// updatedAt=Date.now() on every call, so the 5-min idle sweep re-closing a
|
|
@@ -253,7 +267,7 @@ export function markSessionClosed(id, reason = 'manual', options = {}) {
|
|
|
253
267
|
* of clobbering whatever the resumed session writes next.
|
|
254
268
|
* Returns the new generation, or null if the session file doesn't exist.
|
|
255
269
|
*/
|
|
256
|
-
export function bumpSessionGeneration(id, reason = 'detach') {
|
|
270
|
+
export function bumpSessionGeneration(id, reason = 'detach', options = {}) {
|
|
257
271
|
clearSessionLifecycleCommitError(id);
|
|
258
272
|
// The detach barrier is a canonical write and MUST take the same commit
|
|
259
273
|
// lock as markSessionClosed. Cancellation alone is not a barrier: a writer
|
|
@@ -278,7 +292,7 @@ export function bumpSessionGeneration(id, reason = 'detach') {
|
|
|
278
292
|
_cancelSessionWrites(id);
|
|
279
293
|
_uncacheSessionSummary(id);
|
|
280
294
|
const existing = loadSession(id);
|
|
281
|
-
if (!existing) return
|
|
295
|
+
if (!existing) return _recordAbsent(options);
|
|
282
296
|
const newGen = (typeof existing.generation === 'number' ? existing.generation : 0) + 1;
|
|
283
297
|
const detached = { ...existing, generation: newGen, updatedAt: Date.now(), detachedReason: reason };
|
|
284
298
|
if (!_writeLifecycleRecord(id, detached, existing, reason)) return null;
|