mixdog 1.3.0 → 1.3.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/NOTICE.md +52 -9
- package/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +8 -1
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +54 -4
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +17 -10
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/output-styles/common.md +13 -8
- package/src/rules/agent-mode/AGENT_MODE.md +9 -6
- package/src/rules/agent-mode/MIXY.md +11 -6
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -45
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- 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 +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +62 -47
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/store.mjs +55 -30
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +6 -3
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +4 -0
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-kind.mjs +152 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-route-config.d.mts +1 -0
- package/src/runtime/shared/agent-route-config.mjs +22 -8
- package/src/runtime/shared/agent-store.mjs +149 -0
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +30 -0
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/connector-catalog.d.mts +1 -2
- package/src/runtime/shared/connector-catalog.mjs +2 -4
- package/src/runtime/shared/connector-triggers.mjs +2 -0
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +7 -1
- package/src/runtime/shared/error-presentation.mjs +113 -10
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/plain-preview.mjs +22 -17
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +6 -0
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +44 -2
- package/src/runtime/shared/usage-readout-totals.mjs +15 -13
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +46 -44
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +859 -136
- package/src/session-runtime/agent-room-delivery.mjs +167 -19
- package/src/session-runtime/agent-task-sessions.mjs +831 -69
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +58 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +16 -6
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +2 -2
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +78 -32
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +36 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +11 -2
- package/src/session-runtime/model-route/set-route.mjs +82 -2
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/room-member-gate.mjs +16 -3
- package/src/session-runtime/route-resolution.mjs +21 -19
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +14 -4
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +20 -3
- package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
- package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +47 -358
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +60 -11
- package/src/standalone/daemon.mjs +34 -4
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +599 -342
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -4
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -236
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +185 -20
- package/src/tui/session/transcript-intake.mjs +4 -0
- package/src/tui/session/turn-live-call.mjs +11 -3
- package/src/tui/session/turn.mjs +36 -17
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export const CONNECTOR_TRIGGER_MAX_CHARS = 100;
|
|
7
7
|
|
|
8
8
|
export const CONNECTOR_TRIGGERS = Object.freeze({
|
|
9
|
+
// Not a catalog connector (Mixdog uses the signed-in GitHub CLI), but a
|
|
10
|
+
// user-added `github` MCP server still gets this line.
|
|
9
11
|
github: 'GitHub repos, issues, pull requests, Actions and code search; not local Git history or diffs.',
|
|
10
12
|
notion: 'Notion pages, databases and workspace search; create or update team docs and notes.',
|
|
11
13
|
linear: 'Linear issues, projects, cycles and comments; create, triage or update tickets.',
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* Left untouched on purpose: `desktop`, credentials, onboarding, detection and
|
|
12
12
|
* install flags, caches, and keys that are only ever hand-edited.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { isDeepStrictEqual } from 'node:util';
|
|
17
|
+
import { writeFileAtomicSync } from './atomic-file.mjs';
|
|
17
18
|
import { packDefinitionFromDir } from './shipped-definitions.mjs';
|
|
18
19
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
19
20
|
import { hasOwn, isPlainObject } from './object.mjs';
|
|
@@ -231,7 +232,10 @@ export function backupConfigBeforeDefaultsSeparation({ dataDir = resolvePluginDa
|
|
|
231
232
|
throw err;
|
|
232
233
|
}
|
|
233
234
|
const target = join(dir, 'mixdog-config.json');
|
|
234
|
-
|
|
235
|
+
// Written through an fsynced handle (a plain copy can leave this backup NUL
|
|
236
|
+
// exactly when a crash makes it needed); the folder was claimed above, so
|
|
237
|
+
// nothing can exist at `target`.
|
|
238
|
+
writeFileAtomicSync(target, readFileSync(source), { mode: statSync(source).mode & 0o777 });
|
|
235
239
|
return target;
|
|
236
240
|
}
|
|
237
241
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Delegated approvals: a sub-agent's approval request (tui/session/tool-approval.mjs)
|
|
2
|
+
// is also shown in the top-level conversation the user watches — the root bot's
|
|
3
|
+
// home chat, or the coding Lead's session (human-gate/gate.mjs approvalSurface).
|
|
4
|
+
// It stays ONE request: the sub-agent's queue owns it and its waiter; the
|
|
5
|
+
// top-level session's queue holds a copy whose answer is forwarded to that
|
|
6
|
+
// owner, and the copy is withdrawn as soon as the owner settles (answered in
|
|
7
|
+
// either place, timed out, its turn ended). Like held-request-host.mjs this
|
|
8
|
+
// registry is process-wide and dependency-free.
|
|
9
|
+
|
|
10
|
+
const KEY = Symbol.for('mixdog.delegatedApprovalHost.v1');
|
|
11
|
+
|
|
12
|
+
function registry() {
|
|
13
|
+
globalThis[KEY] ??= { queues: new Set(), pending: new Map(), opener: null };
|
|
14
|
+
return globalThis[KEY];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** `queue`: `{ sessionId() → string, disposed() → boolean, offer(delegated),
|
|
18
|
+
* withdraw(id, result) }` — one session runtime's approval queue. */
|
|
19
|
+
export function registerApprovalQueue(queue) {
|
|
20
|
+
registry().queues.add(queue);
|
|
21
|
+
return () => registry().queues.delete(queue);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `open(sessionId) → Promise`: brings a session's runtime up (the daemon's
|
|
25
|
+
* session service), so a top-level chat that is not loaded can show the copy. */
|
|
26
|
+
export function registerDelegatedApprovalOpener(open) {
|
|
27
|
+
registry().opener = open;
|
|
28
|
+
return () => {
|
|
29
|
+
if (registry().opener === open) registry().opener = null;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function liveQueuesOf(sessionId) {
|
|
34
|
+
const { queues } = registry();
|
|
35
|
+
const live = [];
|
|
36
|
+
for (const queue of queues) {
|
|
37
|
+
if (queue.disposed()) queues.delete(queue);
|
|
38
|
+
else if (queue.sessionId() === sessionId) live.push(queue);
|
|
39
|
+
}
|
|
40
|
+
return live;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function offerTo(sessionId, delegated) {
|
|
44
|
+
const live = liveQueuesOf(sessionId);
|
|
45
|
+
for (const queue of live) queue.offer(delegated);
|
|
46
|
+
return live.length > 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Show `delegated` (`{ id, sessionId, request, answer(...finishArgs) → boolean }`,
|
|
50
|
+
* `sessionId` being the top-level conversation) there, now or once it loads. */
|
|
51
|
+
export function delegateApproval(delegated) {
|
|
52
|
+
const { pending, opener } = registry();
|
|
53
|
+
pending.set(delegated.id, delegated);
|
|
54
|
+
if (offerTo(delegated.sessionId, delegated) || typeof opener !== 'function') return;
|
|
55
|
+
void Promise.resolve()
|
|
56
|
+
.then(() => opener(delegated.sessionId))
|
|
57
|
+
.then(() => {
|
|
58
|
+
if (pending.get(delegated.id) === delegated) offerTo(delegated.sessionId, delegated);
|
|
59
|
+
})
|
|
60
|
+
.catch(() => {});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The owner settled `id`: every copy goes away. `result` (`{ approved, device }`)
|
|
64
|
+
* lets the copy say who answered. */
|
|
65
|
+
export function withdrawDelegatedApproval(id, result = null) {
|
|
66
|
+
const { pending, queues } = registry();
|
|
67
|
+
if (!pending.delete(id)) return;
|
|
68
|
+
for (const queue of queues) queue.withdraw(id, result);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The pending delegated approvals a top-level session should show. */
|
|
72
|
+
export function delegatedApprovalsFor(sessionId) {
|
|
73
|
+
return [...registry().pending.values()].filter((delegated) => delegated.sessionId === sessionId);
|
|
74
|
+
}
|
|
@@ -237,7 +237,9 @@ function typedStreamFailureText(error, text) {
|
|
|
237
237
|
|
|
238
238
|
export function presentErrorText(error, options = {}) {
|
|
239
239
|
const presentation = describeError(error);
|
|
240
|
-
if (['image-size', 'payload-size', 'request'].includes(presentation.kind))
|
|
240
|
+
if (['image-size', 'payload-size', 'request', 'model-unavailable', 'tool-schema'].includes(presentation.kind)) {
|
|
241
|
+
return presentation.summary;
|
|
242
|
+
}
|
|
241
243
|
const kindLine = hasTypedEvidence(error) ? KIND_LINES[presentation.providerKind] : null;
|
|
242
244
|
if (kindLine && presentation.kind !== 'unknown') {
|
|
243
245
|
return typeof kindLine === 'function' ? kindLine(presentation) : kindLine;
|
|
@@ -255,6 +257,10 @@ export function presentErrorText(error, options = {}) {
|
|
|
255
257
|
if (code === 'TOOL_SURFACE_EXCEEDS_CONTEXT' || /\(TOOL_SURFACE_EXCEEDS_CONTEXT\)/.test(text)) {
|
|
256
258
|
return stripErrorPrefix(text);
|
|
257
259
|
}
|
|
260
|
+
// Likewise a latest message that cannot fit names its size and what would
|
|
261
|
+
// help; a wrapper's prefix ("agent compact failed (...): ") is dropped.
|
|
262
|
+
const latestTurn = /The latest message cannot fit this model \(LATEST_TURN_EXCEEDS_CONTEXT\)[\s\S]*$/.exec(text);
|
|
263
|
+
if (latestTurn) return latestTurn[0].replace(' (LATEST_TURN_EXCEEDS_CONTEXT)', '');
|
|
258
264
|
const jsonError = extractJsonError(text);
|
|
259
265
|
if (jsonError) text = jsonError;
|
|
260
266
|
const embeddedError = extractEmbeddedError(text);
|
|
@@ -3,10 +3,14 @@ import { transportErrorText } from './transport-error-text.mjs';
|
|
|
3
3
|
|
|
4
4
|
const DETAILS_LIMIT = 12_000;
|
|
5
5
|
const SUMMARY_LIMIT = 180;
|
|
6
|
+
// "<wrapper>: The latest message cannot fit this model (CODE): <sizes>. <cause and remedy>"
|
|
7
|
+
const LATEST_TURN_RE =
|
|
8
|
+
/The latest message cannot fit this model \(LATEST_TURN_EXCEEDS_CONTEXT\)(: it needs about [\d,]+ tokens, but the [^.]* window leaves [\d,]+ for it\.)([\s\S]*)$/;
|
|
6
9
|
|
|
7
10
|
/** Diagnostic text must be safe before it reaches a view, copy button or log. */
|
|
8
11
|
export function safeErrorDetails(value) {
|
|
9
12
|
let text = String(value ?? '')
|
|
13
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: strips ANSI escape sequences (ESC) from diagnostic text
|
|
10
14
|
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
|
|
11
15
|
.replace(/\r\n?/g, '\n')
|
|
12
16
|
.replace(/\b((?:https?|ssh):\/\/)[^/\s@]+@/gi, '$1[redacted]@')
|
|
@@ -135,7 +139,80 @@ function resetLabel(resetAt) {
|
|
|
135
139
|
const reset = new Date(resetAt);
|
|
136
140
|
const pad = (n) => String(n).padStart(2, '0');
|
|
137
141
|
const time = `${pad(reset.getHours())}:${pad(reset.getMinutes())}`;
|
|
138
|
-
return reset.toDateString() === new Date().toDateString()
|
|
142
|
+
return reset.toDateString() === new Date().toDateString()
|
|
143
|
+
? time
|
|
144
|
+
: `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// A refused request that names its model as unknown, retired or not offered
|
|
148
|
+
// to this account. Read only for request-refusal kinds, so a typed overload,
|
|
149
|
+
// quota or auth failure never reads as a missing model.
|
|
150
|
+
const MODEL_REFUSAL_KINDS = new Set(['invalid_request', 'permission', 'unknown']);
|
|
151
|
+
const MODEL_UNAVAILABLE_CODES = new Set(['model_not_found', 'model_not_supported']);
|
|
152
|
+
// The model must be the subject of the refusal, at the start of a line or
|
|
153
|
+
// sentence ("The model `x` does not exist", "models/x is not found", Ollama's
|
|
154
|
+
// `model "x" not found`), or the refusal names it ("Unknown model", "… is not
|
|
155
|
+
// a valid model ID", Anthropic's bare "model: x"). A model merely mentioned
|
|
156
|
+
// beside some other missing thing ("For model x, tool lookup: route not
|
|
157
|
+
// found") does not count.
|
|
158
|
+
const MODEL_REFUSAL_PREDICATE =
|
|
159
|
+
"(?:(?:is|was|has been)\\s+)?(?:not found|does not exist|doesn'?t exist|not (?:supported|available)|(?:currently\\s+)?unavailable|no longer (?:available|supported)|deprecated|retired|removed)\\b";
|
|
160
|
+
const SENTENCE_START = '(?:^|[.:;!]\\s+)';
|
|
161
|
+
const MODEL_UNAVAILABLE_RE = new RegExp(
|
|
162
|
+
[
|
|
163
|
+
// "The model `x` does not exist", "models/x is not found", Vertex
|
|
164
|
+
// "Publisher Model `…` was not found", Ollama `model "x" not found`.
|
|
165
|
+
`${SENTENCE_START}(?:(?:the|this|that|requested|selected|publisher)\\s+)*models?(?:\\/\\S+|\\s+[\`'"]?[\\w./:@-]+[\`'"]?)?\\s+${MODEL_REFUSAL_PREDICATE}`,
|
|
166
|
+
// OpenAI "The 'gpt-x' model is not supported when using Codex …".
|
|
167
|
+
`${SENTENCE_START}(?:the\\s+)?[\`'"][^\`'"\\n]+[\`'"]\\s+model\\s+${MODEL_REFUSAL_PREDICATE}`,
|
|
168
|
+
`${SENTENCE_START}(?:unknown|invalid|unsupported) model\\b`,
|
|
169
|
+
// OpenRouter: no provider serves this model id.
|
|
170
|
+
`${SENTENCE_START}no endpoints found for [\\w.-]+\\/[\\w.:-]+`,
|
|
171
|
+
'\\bis not a valid model\\b',
|
|
172
|
+
'^model: \\S+$',
|
|
173
|
+
].join('|'),
|
|
174
|
+
'im'
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* True when `error` (or its text) says the requested model is unknown,
|
|
179
|
+
* retired or not offered to this account: a typed model code, or provider
|
|
180
|
+
* wording with the model as the subject of the refusal.
|
|
181
|
+
*/
|
|
182
|
+
export function namesUnavailableModel(error) {
|
|
183
|
+
if (!error) return false;
|
|
184
|
+
if (typeof error === 'string') return MODEL_UNAVAILABLE_RE.test(error);
|
|
185
|
+
const codes = [error.code, error.providerErrorCode, error.error?.code, error.providerError?.code];
|
|
186
|
+
if (codes.some((code) => MODEL_UNAVAILABLE_CODES.has(String(code || '').toLowerCase()))) return true;
|
|
187
|
+
return [error.message, error.error?.message, error.providerError?.message, error.detail]
|
|
188
|
+
.filter((text) => typeof text === 'string' && text)
|
|
189
|
+
.some((text) => MODEL_UNAVAILABLE_RE.test(text));
|
|
190
|
+
}
|
|
191
|
+
const MODEL_UNAVAILABLE_COPY = [
|
|
192
|
+
'model-unavailable',
|
|
193
|
+
"This model isn't available for this account.",
|
|
194
|
+
'Choose another model, or check the model and endpoint in Providers.',
|
|
195
|
+
];
|
|
196
|
+
const NOT_FOUND_SUMMARY = "The provider couldn't find the requested model or endpoint.";
|
|
197
|
+
// A tool definition the provider's schema validator refused (an MCP or custom
|
|
198
|
+
// tool schema), named by the field path or wording each API uses.
|
|
199
|
+
const TOOL_SCHEMA_RE =
|
|
200
|
+
/invalid schema for (?:function|tool)\b|\btools\.\d+\.(?:custom\.)?input_schema\b|\bfunction_declarations\[\d+\]\.parameters\b|\btools\[\d+\]\.function\.parameters\b|\binvalid (?:tool|function) (?:schema|parameters|definition)\b/i;
|
|
201
|
+
const TOOL_SCHEMA_COPY = [
|
|
202
|
+
'tool-schema',
|
|
203
|
+
'The provider rejected a tool definition.',
|
|
204
|
+
'Disable the tool or MCP server named in the details, then try again.',
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
// The first line of a provider message worth showing as-is; '' for a stack
|
|
208
|
+
// frame, a markup page or a raw JSON body.
|
|
209
|
+
function firstReadableLine(clean) {
|
|
210
|
+
const lines = clean
|
|
211
|
+
.split('\n')
|
|
212
|
+
.map((line) => line.trim())
|
|
213
|
+
.filter(Boolean);
|
|
214
|
+
const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
|
|
215
|
+
return first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : '';
|
|
139
216
|
}
|
|
140
217
|
|
|
141
218
|
// Only legacy items (no persisted descriptor) and untyped strings reach this.
|
|
@@ -215,7 +292,9 @@ export function describeError(value) {
|
|
|
215
292
|
if (
|
|
216
293
|
value?.reauthRequired === true ||
|
|
217
294
|
(!typed &&
|
|
218
|
-
/invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(
|
|
295
|
+
/invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(
|
|
296
|
+
raw
|
|
297
|
+
))
|
|
219
298
|
) {
|
|
220
299
|
kind = 'reauth';
|
|
221
300
|
summary = 'Sign-in expired.';
|
|
@@ -223,7 +302,9 @@ export function describeError(value) {
|
|
|
223
302
|
} else if (
|
|
224
303
|
descriptor.kind === 'auth' ||
|
|
225
304
|
(descriptor.kind === 'unknown' &&
|
|
226
|
-
/\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(
|
|
305
|
+
/\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(
|
|
306
|
+
clean
|
|
307
|
+
))
|
|
227
308
|
) {
|
|
228
309
|
kind = 'authentication';
|
|
229
310
|
summary = 'Provider authentication failed';
|
|
@@ -241,6 +322,32 @@ export function describeError(value) {
|
|
|
241
322
|
kind = 'image-size';
|
|
242
323
|
summary = 'An attached image exceeds the provider limit.';
|
|
243
324
|
recovery = 'Reduce the image dimensions or number of images, then try again.';
|
|
325
|
+
} else if (
|
|
326
|
+
clean === MODEL_UNAVAILABLE_COPY[1] ||
|
|
327
|
+
(MODEL_REFUSAL_KINDS.has(descriptor.kind) &&
|
|
328
|
+
(MODEL_UNAVAILABLE_CODES.has(code) || MODEL_UNAVAILABLE_RE.test(clean)))
|
|
329
|
+
) {
|
|
330
|
+
[kind, summary, recovery] = MODEL_UNAVAILABLE_COPY;
|
|
331
|
+
} else if (
|
|
332
|
+
clean === TOOL_SCHEMA_COPY[1] ||
|
|
333
|
+
(['invalid_request', 'unknown'].includes(descriptor.kind) && TOOL_SCHEMA_RE.test(clean))
|
|
334
|
+
) {
|
|
335
|
+
[kind, summary, recovery] = TOOL_SCHEMA_COPY;
|
|
336
|
+
} else if (
|
|
337
|
+
clean === NOT_FOUND_SUMMARY ||
|
|
338
|
+
(MODEL_REFUSAL_KINDS.has(descriptor.kind) && (status === 404 || status === 410))
|
|
339
|
+
) {
|
|
340
|
+
// The provider's own words name what is missing; the next step is the same.
|
|
341
|
+
kind = 'not-found';
|
|
342
|
+
summary = firstReadableLine(clean) || NOT_FOUND_SUMMARY;
|
|
343
|
+
recovery = MODEL_UNAVAILABLE_COPY[2];
|
|
344
|
+
} else if (LATEST_TURN_RE.test(clean)) {
|
|
345
|
+
// The latest message cannot fit the window (compact/runner.mjs): the
|
|
346
|
+
// sizes are the headline, the cause and remedy the next step.
|
|
347
|
+
const [, headline, rest] = LATEST_TURN_RE.exec(clean);
|
|
348
|
+
kind = 'latest-turn-size';
|
|
349
|
+
summary = `The latest message cannot fit this model${headline}`;
|
|
350
|
+
recovery = rest.trim();
|
|
244
351
|
} else if (
|
|
245
352
|
descriptor.kind === 'unknown' &&
|
|
246
353
|
/payload too large|request (?:body )?too large|The request exceeds the size limit/i.test(clean)
|
|
@@ -272,15 +379,11 @@ export function describeError(value) {
|
|
|
272
379
|
summary = 'The provider rejected this request.';
|
|
273
380
|
recovery = 'Review the request details before trying again.';
|
|
274
381
|
} else {
|
|
275
|
-
|
|
276
|
-
.split('\n')
|
|
277
|
-
.map((line) => line.trim())
|
|
278
|
-
.filter(Boolean);
|
|
279
|
-
const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
|
|
280
|
-
summary = first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : 'Something went wrong.';
|
|
382
|
+
summary = firstReadableLine(clean) || 'Something went wrong.';
|
|
281
383
|
}
|
|
282
384
|
summary = summary.replace(/\s+/g, ' ').trim() || 'Something went wrong.';
|
|
283
385
|
if (summary.length > SUMMARY_LIMIT) summary = `${summary.slice(0, SUMMARY_LIMIT - 1).trimEnd()}…`;
|
|
386
|
+
const fingerprintStatus = kind === 'reauth' ? '' : status || '';
|
|
284
387
|
return {
|
|
285
388
|
kind,
|
|
286
389
|
summary,
|
|
@@ -293,6 +396,6 @@ export function describeError(value) {
|
|
|
293
396
|
details,
|
|
294
397
|
// Known structured failures ignore volatile indexes; unknown failures
|
|
295
398
|
// retain their diagnostic identity rather than merging unrelated work.
|
|
296
|
-
fingerprint: kind === 'unknown' ? details : `${kind}:${
|
|
399
|
+
fingerprint: kind === 'unknown' ? details : `${kind}:${fingerprintStatus}`,
|
|
297
400
|
};
|
|
298
401
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync } from 'node:fs';
|
|
2
|
+
import { holdsNoData } from './atomic-file.mjs';
|
|
3
|
+
|
|
4
|
+
const PROBE_BYTES = 4096;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Install check for a downloaded binary or model: false when `path` is absent
|
|
8
|
+
* or its first bytes hold no data (empty, or NUL-filled by a crash that
|
|
9
|
+
* committed the size but not the bytes). Cheaper than hashing a multi-GB file
|
|
10
|
+
* on every use. A file that exists but cannot be opened right now (locked by a
|
|
11
|
+
* scanner or by the process running it) still counts as present.
|
|
12
|
+
*/
|
|
13
|
+
export function fileHasData(path) {
|
|
14
|
+
let fd;
|
|
15
|
+
try {
|
|
16
|
+
fd = openSync(path, 'r');
|
|
17
|
+
const head = Buffer.alloc(PROBE_BYTES);
|
|
18
|
+
return !holdsNoData(head.subarray(0, readSync(fd, head, 0, PROBE_BYTES, 0)));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return err?.code !== 'ENOENT' && err?.code !== 'ENOTDIR';
|
|
21
|
+
} finally {
|
|
22
|
+
if (fd !== undefined) {
|
|
23
|
+
try {
|
|
24
|
+
closeSync(fd);
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface HandoffFile {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
kind: 'image' | 'file' | 'text';
|
|
5
|
+
mimeType: string;
|
|
6
|
+
size: number;
|
|
7
|
+
/** Attachment-store reference when the file was stored. */
|
|
8
|
+
id?: string;
|
|
9
|
+
}
|
|
10
|
+
export function normalizeHandoffFiles(files: unknown): HandoffFile[];
|
|
11
|
+
export function artifactLines(files: unknown, omitted?: number, options?: { ids?: boolean }): string[];
|
|
12
|
+
export interface HandoffRecord {
|
|
13
|
+
id: string;
|
|
14
|
+
attachmentRef: string;
|
|
15
|
+
kind: 'image' | 'file' | 'text';
|
|
16
|
+
name: string;
|
|
17
|
+
mimeType: string;
|
|
18
|
+
size: number;
|
|
19
|
+
}
|
|
20
|
+
export function artifactRecords(files: unknown): HandoffRecord[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The files a bot's or agent's turn handed back (session-runtime/
|
|
2
|
+
// handoff-attachments.mjs collectTurnArtifacts) as the rows that carry them
|
|
3
|
+
// show them: one model-facing line per file — the attachment id that forwards
|
|
4
|
+
// it, or its path when it is too large or of a type no prompt carries — and
|
|
5
|
+
// the message attachment records of the stored ones. Only those records
|
|
6
|
+
// authorize forwarding by id; the lines are information for the model.
|
|
7
|
+
import { escapeHarnessTags } from './shell-output-summary.mjs';
|
|
8
|
+
import { normalizeMessageAttachments } from './transcript-metadata.mjs';
|
|
9
|
+
|
|
10
|
+
const sizeLabel = (size) => {
|
|
11
|
+
const bytes = Number(size) || 0;
|
|
12
|
+
if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`;
|
|
13
|
+
return bytes >= 1024 ? `${Math.round(bytes / 1024)} KB` : `${bytes} B`;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const oneLine = (value) => escapeHarnessTags(String(value ?? '').replace(/[\r\n]+/g, ' '));
|
|
17
|
+
|
|
18
|
+
/** `files` ({ name, mimeType, size, path, id? }) normalized: bounded strings,
|
|
19
|
+
* ids only of the attachment-store shape. */
|
|
20
|
+
export function normalizeHandoffFiles(files) {
|
|
21
|
+
return (Array.isArray(files) ? files : [])
|
|
22
|
+
.filter((file) => file && typeof file === 'object' && typeof file.path === 'string' && file.path)
|
|
23
|
+
.slice(0, 8)
|
|
24
|
+
.map((file) => ({
|
|
25
|
+
name: String(file.name || 'file').slice(0, 200),
|
|
26
|
+
path: file.path.slice(0, 1024),
|
|
27
|
+
kind: ['image', 'file', 'text'].includes(file.kind) ? file.kind : 'file',
|
|
28
|
+
mimeType: String(file.mimeType || 'application/octet-stream').slice(0, 100),
|
|
29
|
+
size: Number(file.size) >= 0 ? Number(file.size) : 0,
|
|
30
|
+
...(/^[a-f0-9]{64}$/.test(String(file.id ?? '')) ? { id: file.id } : {}),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The model-facing list of `files`, empty when there are none. `ids`: the
|
|
35
|
+
* row also records the stored files as its attachments (a task report), so
|
|
36
|
+
* their ids can be forwarded; a plain-text handoff (an agent completion)
|
|
37
|
+
* lists paths only — an id named in text is never trusted. */
|
|
38
|
+
export function artifactLines(files, omitted = 0, { ids = true } = {}) {
|
|
39
|
+
const list = normalizeHandoffFiles(files);
|
|
40
|
+
if (!list.length) return [];
|
|
41
|
+
const attachable = (file) => (ids ? ` [attachment:${file.id}]` : ' (attachable: forward it by path)');
|
|
42
|
+
return [
|
|
43
|
+
`Files from this turn (forward one with attachments: [${ids ? 'id or path' : 'path'}]; link a path to show it):`,
|
|
44
|
+
...list.map(
|
|
45
|
+
(file) =>
|
|
46
|
+
`- ${oneLine(file.name)} (${oneLine(file.mimeType)}, ${sizeLabel(file.size)}) — ${oneLine(file.path)}${
|
|
47
|
+
file.id ? attachable(file) : ' (too large or unsupported to attach; link the path)'
|
|
48
|
+
}`
|
|
49
|
+
),
|
|
50
|
+
...(omitted > 0 ? [`- …and ${omitted} more file(s)`] : []),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The message attachment records of `files` (the stored ones). */
|
|
55
|
+
export function artifactRecords(files) {
|
|
56
|
+
return normalizeMessageAttachments(
|
|
57
|
+
normalizeHandoffFiles(files)
|
|
58
|
+
.filter((file) => file.id)
|
|
59
|
+
.map((file) => ({ ...file, attachmentRef: file.id }))
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -28,7 +28,12 @@ export const ANTIGRAVITY_MODELS = Object.freeze([
|
|
|
28
28
|
low: 'gemini-3.1-pro-low',
|
|
29
29
|
high: 'gemini-3.1-pro-high',
|
|
30
30
|
}),
|
|
31
|
-
|
|
31
|
+
// The gateway serves Argon only through per-tier ids; bare `gemini-4-argon` is a 404.
|
|
32
|
+
fallbackModel('gemini-4-argon', 'Gemini 4 Argon', 'gemini-pro', 1048576, {
|
|
33
|
+
low: 'gemini-4-argon-low',
|
|
34
|
+
medium: 'gemini-4-argon-medium',
|
|
35
|
+
high: 'gemini-4-argon-high',
|
|
36
|
+
}),
|
|
32
37
|
fallbackModel('claude-opus-4-6-thinking', 'Claude Opus 4.6 (Thinking)', null, 250000, 'claude-opus-4-6-thinking', [
|
|
33
38
|
'low',
|
|
34
39
|
'medium',
|
|
@@ -63,6 +63,12 @@ export async function accountProviderSend(provider, instance, send, model, opts
|
|
|
63
63
|
if (!ledger) return;
|
|
64
64
|
recorded.add(owner);
|
|
65
65
|
const usage = result.usage || UNMEASURED_USAGE;
|
|
66
|
+
const durationMs = timed ? Date.now() - startedAt : 0;
|
|
67
|
+
// The one measured request time: the loop's speed figures read it, so
|
|
68
|
+
// the composer and the ledger show the same span.
|
|
69
|
+
if (timed && result && typeof result === 'object') {
|
|
70
|
+
Object.defineProperty(result, 'usageDurationMs', { value: durationMs, configurable: true, writable: true });
|
|
71
|
+
}
|
|
66
72
|
const row = makeUsageRecord({
|
|
67
73
|
id: result.responseId ? undefined : id,
|
|
68
74
|
ts: Date.now(),
|
|
@@ -88,7 +94,7 @@ export async function accountProviderSend(provider, instance, send, model, opts
|
|
|
88
94
|
// The account this send is bound to, so quota history can tell one
|
|
89
95
|
// connected subscription's records from another's.
|
|
90
96
|
account: ACCOUNT_PROVIDERS.includes(provider) ? currentProviderAccountId(provider) : '',
|
|
91
|
-
durationMs
|
|
97
|
+
durationMs,
|
|
92
98
|
});
|
|
93
99
|
// Committed by the ledger worker (batched with concurrent sends); the
|
|
94
100
|
// send still settles only once its row is durable, as before, but the
|
|
@@ -114,7 +114,7 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
|
|
|
114
114
|
// send's diagnostic copy cannot also enter as historical usage.
|
|
115
115
|
const until = Number(ledger.get('liveSince')) || now;
|
|
116
116
|
if (Number(ledger.get('importedThrough')) >= until) return { skipped: true };
|
|
117
|
-
const result = { inserted: 0, sourceRows: 0, unrecordableRows: 0, files: [], traceRows: 0 };
|
|
117
|
+
const result = { inserted: 0, sourceRows: 0, unrecordableRows: 0, tornTraceLines: 0, files: [], traceRows: 0 };
|
|
118
118
|
const accept = (rows) => {
|
|
119
119
|
const normalized = [];
|
|
120
120
|
for (const raw of rows) {
|
|
@@ -140,15 +140,23 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
|
|
|
140
140
|
const original = await readFile(path, 'utf8');
|
|
141
141
|
const lines = original.split(/\r?\n/);
|
|
142
142
|
const rows = [];
|
|
143
|
+
let torn = 0;
|
|
143
144
|
for (let i = 0; i < lines.length; i++) {
|
|
144
145
|
if (!lines[i].trim()) continue;
|
|
145
146
|
let row;
|
|
146
147
|
try {
|
|
147
148
|
row = JSON.parse(lines[i]);
|
|
148
149
|
} catch {
|
|
149
|
-
|
|
150
|
+
// An append a crash tore (cut short, or NUL-filled) is no record: skip
|
|
151
|
+
// it, like the gateway store below, instead of failing every import.
|
|
152
|
+
torn += 1;
|
|
153
|
+
continue;
|
|
150
154
|
}
|
|
151
|
-
if (row
|
|
155
|
+
if (row?.kind === 'usage_raw' || row?.kind === 'usage') rows.push(row);
|
|
156
|
+
}
|
|
157
|
+
if (torn > 0) {
|
|
158
|
+
process.stderr.write(`[usage-ledger] warning: ${path} has ${torn} torn trace line(s); skipped\n`);
|
|
159
|
+
result.tornTraceLines += torn;
|
|
152
160
|
}
|
|
153
161
|
accept(rows);
|
|
154
162
|
result.files.push({ path, sha256: createHash('sha256').update(original).digest('hex'), rows: rows.length });
|
|
@@ -160,29 +168,40 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
|
|
|
160
168
|
// The JSONL store supersedes the legacy JSON file (it is migrated from it).
|
|
161
169
|
const gatewayJsonl = await optionalFile(join(dataDir, 'gateway-usage.local.jsonl'));
|
|
162
170
|
const gateway = gatewayJsonl === null ? await optionalFile(join(dataDir, 'gateway-usage.local.json')) : null;
|
|
163
|
-
|
|
171
|
+
// A missing file is absence. Empty, unparsable, or wrong-shaped JSON is not:
|
|
172
|
+
// skip only that legacy import and leave importedThrough unset so a repair
|
|
173
|
+
// is retried instead of short-circuiting forever.
|
|
174
|
+
let legacyReady = true;
|
|
175
|
+
let gatewayEvents = null;
|
|
176
|
+
if (gatewayJsonl !== null) {
|
|
177
|
+
gatewayEvents = gatewayJsonl.split('\n').flatMap((line) => {
|
|
178
|
+
try {
|
|
179
|
+
return line.trim() ? [JSON.parse(line)] : [];
|
|
180
|
+
} catch {
|
|
181
|
+
return []; // torn append
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} else if (gateway !== null) {
|
|
185
|
+
try {
|
|
186
|
+
const events = JSON.parse(gateway)?.events;
|
|
187
|
+
gatewayEvents = Array.isArray(events) ? events : [];
|
|
188
|
+
} catch (error) {
|
|
189
|
+
legacyReady = false;
|
|
190
|
+
process.stderr.write(
|
|
191
|
+
`[usage-ledger] warning: legacy gateway usage file gateway-usage.local.json is empty or corrupt (${error.message}); skipping its import\n`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (gatewayEvents)
|
|
164
196
|
accept(
|
|
165
|
-
(
|
|
166
|
-
? gatewayJsonl.split('\n').flatMap((line) => {
|
|
167
|
-
try {
|
|
168
|
-
return line.trim() ? [JSON.parse(line)] : [];
|
|
169
|
-
} catch {
|
|
170
|
-
return []; // torn append
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
: JSON.parse(gateway).events || []
|
|
174
|
-
).map((event) => ({
|
|
197
|
+
gatewayEvents.map((event) => ({
|
|
175
198
|
...event,
|
|
176
199
|
kind: 'usage',
|
|
177
200
|
session_id: event.sessionId,
|
|
178
201
|
}))
|
|
179
202
|
);
|
|
180
|
-
// A missing file is absence. Empty, unparsable, or wrong-shaped JSON is not:
|
|
181
|
-
// skip only this legacy import and leave importedThrough unset so a repair
|
|
182
|
-
// is retried instead of short-circuiting forever.
|
|
183
203
|
const legacyPath = join(dataDir, 'usage-rollup.local.json');
|
|
184
204
|
const legacy = await optionalFile(legacyPath);
|
|
185
|
-
let legacyReady = true;
|
|
186
205
|
if (legacy !== null) {
|
|
187
206
|
let days;
|
|
188
207
|
try {
|
|
@@ -1,10 +1,84 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
boundedCauseChain,
|
|
3
|
+
providerErrorKind,
|
|
4
|
+
typedCodeKind,
|
|
5
|
+
} from '../agent/orchestrator/providers/provider-error-kind.mjs';
|
|
2
6
|
import { readStreamOutcome } from '../agent/orchestrator/providers/lib/stream-outcome.mjs';
|
|
7
|
+
import { namesUnavailableModel } from './error-presentation.mjs';
|
|
3
8
|
|
|
4
|
-
|
|
9
|
+
// When a route's model fails, does the next model in its fallback chain get
|
|
10
|
+
// the request? Only for a refusal scoped to THIS provider, account or model —
|
|
11
|
+
// another provider may serve the identical request:
|
|
12
|
+
// quota_exhausted / entitlement / billing — the plan or account is spent;
|
|
13
|
+
// rate_limited / overloaded / service_tier_unavailable — this provider is
|
|
14
|
+
// saturated once its own retries are spent;
|
|
15
|
+
// auth / permission — the provider's own sign-in refresh already failed, or
|
|
16
|
+
// the account, region or tier is refused;
|
|
17
|
+
// transient — only when the server answered (a typed status or code): its
|
|
18
|
+
// retry ladder is spent. A bare network failure would fail on every
|
|
19
|
+
// provider, so it stays with the transport recovery;
|
|
20
|
+
// invalid_request on 404/410, a typed model code, or provider wording with
|
|
21
|
+
// the model as the refused subject (namesUnavailableModel) — the model is
|
|
22
|
+
// unknown, retired or not offered to this account.
|
|
23
|
+
// A request that fails the same way on any model never hops: request too
|
|
24
|
+
// large, a refused tool schema or other invalid request, a policy refusal,
|
|
25
|
+
// a cancel, a stall and any untyped failure. A context overflow hops only to
|
|
26
|
+
// a model with a larger window (`contextGrows`); compaction runs first.
|
|
27
|
+
// Whatever the kind, nothing may have been produced: no visible or buffered
|
|
28
|
+
// output, no tool call started or dispatched.
|
|
29
|
+
const PROVIDER_SCOPED_KINDS = new Set([
|
|
30
|
+
'quota_exhausted',
|
|
31
|
+
'entitlement',
|
|
32
|
+
'billing',
|
|
33
|
+
'rate_limited',
|
|
34
|
+
'overloaded',
|
|
35
|
+
'service_tier_unavailable',
|
|
36
|
+
'auth',
|
|
37
|
+
'permission',
|
|
38
|
+
]);
|
|
39
|
+
const MODEL_UNAVAILABLE_CODES = new Set(['model_not_found', 'model_not_supported']);
|
|
5
40
|
|
|
6
|
-
|
|
7
|
-
|
|
41
|
+
/** 'hop', 'context' (a context overflow: hop only to a larger window) or 'stay'. */
|
|
42
|
+
// A recovery layer may wrap the provider failure (a context overflow it could
|
|
43
|
+
// not compact away): what the original request exposed lives on the cause.
|
|
44
|
+
// Every node of the chain counts, so a wrapper never launders exposure.
|
|
45
|
+
function chainOutcome(error) {
|
|
46
|
+
return readStreamOutcome(...(error && typeof error === 'object' ? boundedCauseChain(error) : [error]));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function routeFallbackVerdict(error, { signal, outcome = chainOutcome(error), committed = false } = {}) {
|
|
50
|
+
if (signal?.aborted || committed || outcome.userAbort || outcome.stallObserved) return 'stay';
|
|
51
|
+
if (!outcome.replaySafe || outcome.observedOutput || outcome.toolCallsStarted || outcome.pendingToolInput) {
|
|
52
|
+
return 'stay';
|
|
53
|
+
}
|
|
54
|
+
const { kind, status, code } = providerErrorKind(error);
|
|
55
|
+
if (PROVIDER_SCOPED_KINDS.has(kind)) return 'hop';
|
|
56
|
+
if (kind === 'transient') return status >= 408 || typedCodeKind(error) === 'transient' ? 'hop' : 'stay';
|
|
57
|
+
if (kind === 'invalid_request') {
|
|
58
|
+
return status === 404 || status === 410 || MODEL_UNAVAILABLE_CODES.has(code) || namesUnavailableModel(error)
|
|
59
|
+
? 'hop'
|
|
60
|
+
: 'stay';
|
|
61
|
+
}
|
|
62
|
+
return kind === 'context_overflow' ? 'context' : 'stay';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function canFallbackRoute(error, { contextGrows = false, ...options } = {}) {
|
|
66
|
+
const verdict = routeFallbackVerdict(error, options);
|
|
67
|
+
return verdict === 'hop' || (verdict === 'context' && contextGrows === true);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const CAPACITY_KINDS = new Set(['rate_limited', 'quota_exhausted', 'overloaded', 'transient']);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The narrower rule a stateless tool route (native web search) keeps: only a
|
|
74
|
+
* capacity refusal the server stated (429 / 503, or an account-pool quota
|
|
75
|
+
* refusal) moves to the next provider; a denied or failed credential
|
|
76
|
+
* surfaces so the user fixes that route.
|
|
77
|
+
*/
|
|
78
|
+
// It also keeps its stricter stream guards: any started or terminal stream
|
|
79
|
+
// stays on its route, since a tool call cannot prove nothing was generated.
|
|
80
|
+
export function canFallbackCapacityRoute(error, { signal, outcome = readStreamOutcome(error) } = {}) {
|
|
81
|
+
if (signal?.aborted || outcome.userAbort || outcome.stallObserved) return false;
|
|
8
82
|
if (
|
|
9
83
|
!outcome.replaySafe ||
|
|
10
84
|
outcome.observedOutput ||
|
|
@@ -15,8 +89,6 @@ export function canFallbackRoute(error, { signal, outcome = readStreamOutcome(er
|
|
|
15
89
|
)
|
|
16
90
|
return false;
|
|
17
91
|
const { kind, status } = providerErrorKind(error);
|
|
18
|
-
if (!
|
|
19
|
-
// A rejected HTTP request or a provider account-pool refusal proves no
|
|
20
|
-
// generation started. A transport disconnect alone does not.
|
|
92
|
+
if (!CAPACITY_KINDS.has(kind)) return false;
|
|
21
93
|
return status === 429 || status === 503 || (kind === 'quota_exhausted' && error?.quotaExhausted != null);
|
|
22
94
|
}
|