@kr78/pi-coding-agent 17.2.10
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/CHANGELOG.md +14861 -0
- package/README.md +35 -0
- package/dist/CHANGELOG-chtdxh1z.md +14861 -0
- package/dist/cli.js +20483 -0
- package/dist/template-c2hyaytt.js +1653 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +104 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/extensions/README.md +142 -0
- package/examples/extensions/api-demo.ts +78 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +31 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +549 -0
- package/examples/extensions/reload-runtime.ts +38 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +17 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +46 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +82 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/12-redis-sessions.ts +54 -0
- package/examples/sdk/13-sql-sessions.ts +61 -0
- package/examples/sdk/README.md +169 -0
- package/package.json +587 -0
- package/scripts/bench-guard.ts +71 -0
- package/scripts/bench-title-models.ts +332 -0
- package/scripts/build-binary.ts +117 -0
- package/scripts/bundle-dist.ts +123 -0
- package/scripts/compile-binary.ts +69 -0
- package/scripts/embed-mupdf-wasm.ts +67 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-aria-snapshot.ts +134 -0
- package/scripts/generate-docs-index.ts +58 -0
- package/scripts/generate-share-viewer.ts +34 -0
- package/scripts/legacy-pi-virtual-module.ts +205 -0
- package/scripts/measure-prompt-tokens.ts +63 -0
- package/scripts/omp +51 -0
- package/scripts/omp.ts +19 -0
- package/scripts/security-compare.ts +40 -0
- package/src/advisor/advise-tool.ts +234 -0
- package/src/advisor/config.ts +341 -0
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +6 -0
- package/src/advisor/runtime.ts +1412 -0
- package/src/advisor/transcript-recorder.ts +215 -0
- package/src/advisor/watchdog.ts +135 -0
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +847 -0
- package/src/auto-thinking/classifier.ts +220 -0
- package/src/autolearn/controller.ts +152 -0
- package/src/autolearn/managed-skills.ts +255 -0
- package/src/autoresearch/command-resume.md +14 -0
- package/src/autoresearch/dashboard.ts +436 -0
- package/src/autoresearch/git.ts +331 -0
- package/src/autoresearch/helpers.ts +218 -0
- package/src/autoresearch/index.ts +541 -0
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +103 -0
- package/src/autoresearch/resume-message.md +10 -0
- package/src/autoresearch/state.ts +273 -0
- package/src/autoresearch/storage.ts +700 -0
- package/src/autoresearch/tools/init-experiment.ts +268 -0
- package/src/autoresearch/tools/log-experiment.ts +520 -0
- package/src/autoresearch/tools/run-experiment.ts +407 -0
- package/src/autoresearch/tools/update-notes.ts +109 -0
- package/src/autoresearch/types.ts +168 -0
- package/src/capability/context-file.ts +44 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +117 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +467 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +115 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule-buckets.ts +66 -0
- package/src/capability/rule.ts +298 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +69 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +187 -0
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +368 -0
- package/src/cli/auth-broker-cli.ts +940 -0
- package/src/cli/auth-gateway-cli.ts +674 -0
- package/src/cli/bench-cli.ts +990 -0
- package/src/cli/browser-relay-cli.ts +119 -0
- package/src/cli/classify-install-target.ts +76 -0
- package/src/cli/claude-trace-cli.ts +795 -0
- package/src/cli/command-help.ts +107 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/completion-gen.ts +550 -0
- package/src/cli/config-cli.ts +459 -0
- package/src/cli/dry-balance-cli.ts +864 -0
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/file-processor.ts +132 -0
- package/src/cli/flag-tables.ts +367 -0
- package/src/cli/gallery-cli.ts +272 -0
- package/src/cli/gallery-fixtures/agentic.ts +420 -0
- package/src/cli/gallery-fixtures/codeintel.ts +187 -0
- package/src/cli/gallery-fixtures/edit.ts +254 -0
- package/src/cli/gallery-fixtures/fs.ts +245 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +46 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +177 -0
- package/src/cli/gallery-fixtures/search.ts +135 -0
- package/src/cli/gallery-fixtures/shell.ts +241 -0
- package/src/cli/gallery-fixtures/types.ts +57 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli/gc-cli.ts +1566 -0
- package/src/cli/grep-cli.ts +161 -0
- package/src/cli/grievances-cli.ts +256 -0
- package/src/cli/help-extra.ts +89 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/models-cli.ts +385 -0
- package/src/cli/plugin-cli.ts +996 -0
- package/src/cli/profile-alias.ts +369 -0
- package/src/cli/profile-bootstrap.ts +233 -0
- package/src/cli/read-cli.ts +99 -0
- package/src/cli/session-picker.ts +110 -0
- package/src/cli/setup-cli.ts +312 -0
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli/shell-cli.ts +176 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/startup-cwd.ts +58 -0
- package/src/cli/stats-cli.ts +229 -0
- package/src/cli/thinking-levels.ts +7 -0
- package/src/cli/tiny-models-cli.ts +153 -0
- package/src/cli/ttsr-cli.ts +1013 -0
- package/src/cli/update-cli.ts +1184 -0
- package/src/cli/usage-cli.ts +1084 -0
- package/src/cli/usage-error.ts +7 -0
- package/src/cli/web-search-cli.ts +144 -0
- package/src/cli/worktree-cli.ts +311 -0
- package/src/cli-commands.ts +308 -0
- package/src/cli.ts +428 -0
- package/src/collab/crypto.ts +63 -0
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +764 -0
- package/src/collab/host.ts +692 -0
- package/src/collab/protocol.ts +296 -0
- package/src/collab/relay-client.ts +282 -0
- package/src/collab/replication-shrink.ts +111 -0
- package/src/commands/acp.ts +35 -0
- package/src/commands/agents.ts +58 -0
- package/src/commands/auth-broker.ts +100 -0
- package/src/commands/auth-gateway.ts +70 -0
- package/src/commands/bench.ts +64 -0
- package/src/commands/browser-relay.ts +53 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/commit.ts +47 -0
- package/src/commands/complete.ts +67 -0
- package/src/commands/completions.ts +61 -0
- package/src/commands/config.ts +52 -0
- package/src/commands/dry-balance.ts +43 -0
- package/src/commands/gallery.ts +61 -0
- package/src/commands/gc.ts +47 -0
- package/src/commands/grep.ts +49 -0
- package/src/commands/grievances.ts +52 -0
- package/src/commands/install.ts +107 -0
- package/src/commands/join.ts +40 -0
- package/src/commands/launch-help.ts +116 -0
- package/src/commands/launch.ts +34 -0
- package/src/commands/models.ts +61 -0
- package/src/commands/plugin.ts +79 -0
- package/src/commands/read.ts +39 -0
- package/src/commands/say.ts +146 -0
- package/src/commands/setup.ts +68 -0
- package/src/commands/share.ts +71 -0
- package/src/commands/shell.ts +30 -0
- package/src/commands/ssh.ts +61 -0
- package/src/commands/stats.ts +30 -0
- package/src/commands/tiny-models.ts +36 -0
- package/src/commands/token.ts +164 -0
- package/src/commands/ttsr.ts +125 -0
- package/src/commands/update.ts +34 -0
- package/src/commands/usage.ts +55 -0
- package/src/commands/web-search.ts +43 -0
- package/src/commands/worktree.ts +63 -0
- package/src/commit/agentic/agent.ts +322 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +383 -0
- package/src/commit/agentic/lock-files.ts +107 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +148 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +52 -0
- package/src/commit/agentic/tools/git-overview.ts +62 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +147 -0
- package/src/commit/agentic/tools/propose-commit.ts +109 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +11 -0
- package/src/commit/agentic/tools/split-commit.ts +241 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +64 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +107 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +101 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +49 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +95 -0
- package/src/commit/pipeline.ts +244 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/shared-llm.ts +70 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +58 -0
- package/src/config/api-key-resolver.ts +81 -0
- package/src/config/append-only-context-mode.ts +76 -0
- package/src/config/config-file.ts +347 -0
- package/src/config/inline-tool-descriptors-mode.ts +26 -0
- package/src/config/keybindings.ts +703 -0
- package/src/config/mcp-schema.json +252 -0
- package/src/config/model-discovery.ts +1077 -0
- package/src/config/model-registry.ts +2822 -0
- package/src/config/model-resolver.ts +2115 -0
- package/src/config/model-roles.ts +113 -0
- package/src/config/models-config-schema-bundle.ts +315 -0
- package/src/config/models-config-schema.ts +14 -0
- package/src/config/models-config.ts +130 -0
- package/src/config/prompt-templates.ts +205 -0
- package/src/config/provider-globals.ts +25 -0
- package/src/config/resolve-config-value.ts +95 -0
- package/src/config/service-tier.ts +146 -0
- package/src/config/settings-schema.ts +5887 -0
- package/src/config/settings.ts +2394 -0
- package/src/config.ts +242 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +960 -0
- package/src/dap/client.ts +1043 -0
- package/src/dap/config.ts +480 -0
- package/src/dap/defaults.json +212 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1841 -0
- package/src/dap/types.ts +611 -0
- package/src/debug/index.ts +584 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +966 -0
- package/src/debug/profiler.ts +168 -0
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/raw-sse-buffer.ts +421 -0
- package/src/debug/raw-sse.ts +312 -0
- package/src/debug/remote-debugger.ts +151 -0
- package/src/debug/report-bundle.ts +411 -0
- package/src/debug/system-info.ts +111 -0
- package/src/debug/terminal-info.ts +124 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +300 -0
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-defaults.ts +39 -0
- package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
- package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
- package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
- package/src/discovery/builtin-rules/go-ioutil.md +37 -0
- package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
- package/src/discovery/builtin-rules/go-new-expr.md +44 -0
- package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
- package/src/discovery/builtin-rules/go-range-int.md +45 -0
- package/src/discovery/builtin-rules/index.ts +74 -0
- package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
- package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
- package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
- package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
- package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
- package/src/discovery/builtin-rules/rs-result-type.md +20 -0
- package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
- package/src/discovery/builtin-rules/ts-import-type.md +43 -0
- package/src/discovery/builtin-rules/ts-no-any.md +66 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
- package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
- package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/builtin-rules/ts-set-map.md +28 -0
- package/src/discovery/builtin.ts +945 -0
- package/src/discovery/claude-plugins.ts +640 -0
- package/src/discovery/claude.ts +591 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +553 -0
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/cursor.ts +223 -0
- package/src/discovery/gemini.ts +386 -0
- package/src/discovery/github.ts +337 -0
- package/src/discovery/helpers.ts +1183 -0
- package/src/discovery/index.ts +82 -0
- package/src/discovery/mcp-json.ts +182 -0
- package/src/discovery/omp-extension-roots.ts +272 -0
- package/src/discovery/omp-plugins.ts +409 -0
- package/src/discovery/opencode.ts +441 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +77 -0
- package/src/discovery/vscode.ts +106 -0
- package/src/discovery/windsurf.ts +149 -0
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +1008 -0
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/file-snapshot-store.ts +148 -0
- package/src/edit/hashline/block-resolver.ts +33 -0
- package/src/edit/hashline/diff.ts +402 -0
- package/src/edit/hashline/execute.ts +313 -0
- package/src/edit/hashline/filesystem.ts +247 -0
- package/src/edit/hashline/index.ts +5 -0
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/hashline/params.ts +12 -0
- package/src/edit/index.ts +699 -0
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +53 -0
- package/src/edit/modes/patch.ts +1958 -0
- package/src/edit/modes/replace.ts +1191 -0
- package/src/edit/normalize.ts +345 -0
- package/src/edit/notebook.ts +242 -0
- package/src/edit/read-file.ts +25 -0
- package/src/edit/renderer.ts +998 -0
- package/src/edit/snapshot-details.ts +77 -0
- package/src/edit/streaming.ts +710 -0
- package/src/eval/agent-bridge.ts +224 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/backend.ts +71 -0
- package/src/eval/bridge-timeout.ts +65 -0
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/completion-bridge.ts +212 -0
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/executor-base.ts +562 -0
- package/src/eval/idle-timeout.ts +91 -0
- package/src/eval/index.ts +6 -0
- package/src/eval/jl/executor.ts +551 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +236 -0
- package/src/eval/jl/prelude.jl +736 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +666 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/context-manager.ts +872 -0
- package/src/eval/js/executor.ts +178 -0
- package/src/eval/js/index.ts +41 -0
- package/src/eval/js/process-entry.ts +31 -0
- package/src/eval/js/shared/helpers.ts +170 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/local-module-loader.ts +364 -0
- package/src/eval/js/shared/prelude.ts +2 -0
- package/src/eval/js/shared/prelude.txt +294 -0
- package/src/eval/js/shared/rewrite-imports.ts +550 -0
- package/src/eval/js/shared/runtime.ts +590 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +163 -0
- package/src/eval/js/worker-core.ts +380 -0
- package/src/eval/js/worker-entry.ts +37 -0
- package/src/eval/js/worker-protocol.ts +47 -0
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +643 -0
- package/src/eval/py/index.ts +57 -0
- package/src/eval/py/kernel.ts +235 -0
- package/src/eval/py/prelude.py +673 -0
- package/src/eval/py/prelude.ts +3 -0
- package/src/eval/py/runner.py +1425 -0
- package/src/eval/py/runtime.ts +276 -0
- package/src/eval/py/spawn-options.ts +134 -0
- package/src/eval/py/tool-bridge.ts +201 -0
- package/src/eval/rb/executor.ts +511 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +231 -0
- package/src/eval/rb/prelude.rb +551 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +581 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/session-id.ts +8 -0
- package/src/eval/types.ts +48 -0
- package/src/exa/index.ts +2 -0
- package/src/exa/mcp-client.ts +370 -0
- package/src/exa/types.ts +69 -0
- package/src/exec/bash-executor.ts +627 -0
- package/src/exec/direnv.ts +145 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/non-interactive-env.ts +118 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +320 -0
- package/src/export/html/share-loader.js +102 -0
- package/src/export/html/template.css +1355 -0
- package/src/export/html/template.html +55 -0
- package/src/export/html/template.js +1653 -0
- package/src/export/html/tool-views.generated.js +35 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/html/web-palette.ts +126 -0
- package/src/export/share.ts +686 -0
- package/src/export/ttsr.ts +590 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +242 -0
- package/src/extensibility/custom-commands/types.ts +119 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +301 -0
- package/src/extensibility/custom-tools/types.ts +287 -0
- package/src/extensibility/custom-tools/wrapper.ts +50 -0
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/get-commands-handler.ts +78 -0
- package/src/extensibility/extensions/index.ts +16 -0
- package/src/extensibility/extensions/load-errors.ts +13 -0
- package/src/extensibility/extensions/loader.ts +663 -0
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/model-api.ts +39 -0
- package/src/extensibility/extensions/runner.ts +1396 -0
- package/src/extensibility/extensions/types.ts +1603 -0
- package/src/extensibility/extensions/wrapper.ts +395 -0
- package/src/extensibility/hooks/index.ts +6 -0
- package/src/extensibility/hooks/loader.ts +243 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +124 -0
- package/src/extensibility/hooks/types.ts +612 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
- package/src/extensibility/legacy-typebox.ts +101 -0
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +367 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +201 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
- package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
- package/src/extensibility/plugins/loader.ts +535 -0
- package/src/extensibility/plugins/manager.ts +1142 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +927 -0
- package/src/extensibility/plugins/marketplace/registry.ts +192 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +192 -0
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/extensibility/plugins/parser.ts +107 -0
- package/src/extensibility/plugins/runtime-config.ts +9 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/extensibility/shared-events.ts +401 -0
- package/src/extensibility/skills.ts +531 -0
- package/src/extensibility/slash-commands.ts +131 -0
- package/src/extensibility/tool-event-input.ts +80 -0
- package/src/extensibility/tool-proxy.ts +28 -0
- package/src/extensibility/utils.ts +192 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +521 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +251 -0
- package/src/hindsight/backend.ts +354 -0
- package/src/hindsight/bank.ts +156 -0
- package/src/hindsight/client.ts +680 -0
- package/src/hindsight/config.ts +193 -0
- package/src/hindsight/content.ts +266 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +429 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +530 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/index.ts +66 -0
- package/src/internal-urls/agent-protocol.ts +180 -0
- package/src/internal-urls/artifact-protocol.ts +151 -0
- package/src/internal-urls/docs-index.ts +102 -0
- package/src/internal-urls/filesystem-resource.ts +34 -0
- package/src/internal-urls/history-protocol.ts +198 -0
- package/src/internal-urls/index.ts +28 -0
- package/src/internal-urls/issue-pr-protocol.ts +594 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/local-protocol.ts +471 -0
- package/src/internal-urls/mcp-protocol.ts +168 -0
- package/src/internal-urls/memory-protocol.ts +380 -0
- package/src/internal-urls/omp-protocol.ts +94 -0
- package/src/internal-urls/parse.ts +103 -0
- package/src/internal-urls/registry-helpers.ts +132 -0
- package/src/internal-urls/router.ts +153 -0
- package/src/internal-urls/rule-protocol.ts +45 -0
- package/src/internal-urls/security-protocol.ts +261 -0
- package/src/internal-urls/skill-protocol.ts +131 -0
- package/src/internal-urls/ssh-protocol.ts +368 -0
- package/src/internal-urls/types.ts +196 -0
- package/src/internal-urls/vault-protocol.ts +940 -0
- package/src/internal-urls/xd-protocol.ts +46 -0
- package/src/irc/bus.ts +380 -0
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/launch/broker.ts +1368 -0
- package/src/launch/client.ts +521 -0
- package/src/launch/ensure.ts +73 -0
- package/src/launch/paths.ts +14 -0
- package/src/launch/presence.ts +82 -0
- package/src/launch/protocol.ts +448 -0
- package/src/launch/spawn-options.ts +17 -0
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/launch/terminal-output.ts +46 -0
- package/src/lib/xai-http.ts +150 -0
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +517 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +422 -0
- package/src/live/visualizer.ts +221 -0
- package/src/live/voices.ts +18 -0
- package/src/lsp/client.ts +1465 -0
- package/src/lsp/clients/biome-client.ts +263 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +85 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +549 -0
- package/src/lsp/defaults.json +499 -0
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/edits.ts +288 -0
- package/src/lsp/format-options.ts +119 -0
- package/src/lsp/index.ts +2821 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/mux/daemon.ts +348 -0
- package/src/lsp/mux/protocol.ts +96 -0
- package/src/lsp/mux/server.ts +797 -0
- package/src/lsp/render.ts +668 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +479 -0
- package/src/lsp/utils.ts +747 -0
- package/src/main.ts +1799 -0
- package/src/markit/NOTICE +32 -0
- package/src/markit/converters/docx.ts +56 -0
- package/src/markit/converters/epub.ts +136 -0
- package/src/markit/converters/pdf/columns.ts +103 -0
- package/src/markit/converters/pdf/extract.ts +598 -0
- package/src/markit/converters/pdf/grid.ts +780 -0
- package/src/markit/converters/pdf/headers.ts +106 -0
- package/src/markit/converters/pdf/index.ts +146 -0
- package/src/markit/converters/pdf/render.ts +501 -0
- package/src/markit/converters/pdf/types.ts +84 -0
- package/src/markit/converters/pptx.ts +325 -0
- package/src/markit/converters/xlsx.ts +173 -0
- package/src/markit/index.ts +2 -0
- package/src/markit/registry.ts +59 -0
- package/src/markit/types.ts +35 -0
- package/src/mcp/client.ts +511 -0
- package/src/mcp/config-writer.ts +377 -0
- package/src/mcp/config.ts +385 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +122 -0
- package/src/mcp/loader.ts +125 -0
- package/src/mcp/manager.ts +1524 -0
- package/src/mcp/oauth-credentials.ts +104 -0
- package/src/mcp/oauth-discovery.ts +587 -0
- package/src/mcp/oauth-flow.ts +830 -0
- package/src/mcp/render.ts +214 -0
- package/src/mcp/request-id.ts +24 -0
- package/src/mcp/smithery-auth.ts +108 -0
- package/src/mcp/smithery-connect.ts +154 -0
- package/src/mcp/smithery-registry.ts +500 -0
- package/src/mcp/startup-events.ts +116 -0
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/tool-bridge.ts +691 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +508 -0
- package/src/mcp/transports/index.ts +7 -0
- package/src/mcp/transports/sse.ts +372 -0
- package/src/mcp/transports/stdio.ts +905 -0
- package/src/mcp/types.ts +467 -0
- package/src/memories/index.ts +1434 -0
- package/src/memories/storage.ts +578 -0
- package/src/memory-backend/index.ts +19 -0
- package/src/memory-backend/local-backend.ts +47 -0
- package/src/memory-backend/messages.ts +19 -0
- package/src/memory-backend/off-backend.ts +25 -0
- package/src/memory-backend/resolve.ts +25 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/memory-backend/types.ts +166 -0
- package/src/mnemopi/backend.ts +629 -0
- package/src/mnemopi/config.ts +267 -0
- package/src/mnemopi/embed-client.ts +293 -0
- package/src/mnemopi/embed-protocol.ts +35 -0
- package/src/mnemopi/embed-worker.ts +114 -0
- package/src/mnemopi/index.ts +3 -0
- package/src/mnemopi/state.ts +940 -0
- package/src/modes/acp/acp-agent.ts +2595 -0
- package/src/modes/acp/acp-client-bridge.ts +154 -0
- package/src/modes/acp/acp-event-mapper.ts +1084 -0
- package/src/modes/acp/acp-mode.ts +48 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/advisor-config.ts +635 -0
- package/src/modes/components/advisor-message.ts +109 -0
- package/src/modes/components/agent-dashboard.ts +1252 -0
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +1126 -0
- package/src/modes/components/agent-transcript-viewer.ts +647 -0
- package/src/modes/components/ask-dialog.ts +1017 -0
- package/src/modes/components/assistant-message.ts +978 -0
- package/src/modes/components/background-tan-message.ts +36 -0
- package/src/modes/components/bash-execution.ts +233 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/btw-panel.ts +161 -0
- package/src/modes/components/cache-invalidation-marker.ts +110 -0
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/chat-transcript-builder.ts +518 -0
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/collab-prompt-message.ts +32 -0
- package/src/modes/components/compaction-summary-message.ts +221 -0
- package/src/modes/components/copy-selector.ts +218 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +1036 -0
- package/src/modes/components/custom-message.ts +70 -0
- package/src/modes/components/diff.ts +254 -0
- package/src/modes/components/dynamic-border.ts +37 -0
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/eval-execution.ts +169 -0
- package/src/modes/components/execution-shared.ts +101 -0
- package/src/modes/components/extensions/extension-dashboard.ts +492 -0
- package/src/modes/components/extensions/extension-list.ts +507 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +326 -0
- package/src/modes/components/extensions/state-manager.ts +648 -0
- package/src/modes/components/extensions/types.ts +186 -0
- package/src/modes/components/footer.ts +274 -0
- package/src/modes/components/history-search.ts +268 -0
- package/src/modes/components/hook-editor.ts +213 -0
- package/src/modes/components/hook-input.ts +87 -0
- package/src/modes/components/hook-message.ts +67 -0
- package/src/modes/components/hook-selector.ts +691 -0
- package/src/modes/components/index.ts +42 -0
- package/src/modes/components/keybinding-hints.ts +56 -0
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/login-dialog.ts +197 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1413 -0
- package/src/modes/components/message-frame.ts +98 -0
- package/src/modes/components/model-browser.ts +888 -0
- package/src/modes/components/model-hub.ts +2014 -0
- package/src/modes/components/model-picker.ts +237 -0
- package/src/modes/components/move-overlay.ts +293 -0
- package/src/modes/components/oauth-selector.ts +474 -0
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/overlay-box.ts +109 -0
- package/src/modes/components/pause-screen.ts +208 -0
- package/src/modes/components/plan-review-overlay.ts +1226 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/plugin-selector.ts +100 -0
- package/src/modes/components/plugin-settings.ts +745 -0
- package/src/modes/components/queue-mode-selector.ts +61 -0
- package/src/modes/components/read-tool-group.ts +856 -0
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/segment-track.ts +89 -0
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/session-selector.ts +1035 -0
- package/src/modes/components/settings-defs.ts +267 -0
- package/src/modes/components/settings-selector.ts +1445 -0
- package/src/modes/components/show-images-selector.ts +50 -0
- package/src/modes/components/skill-message.ts +110 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/status-line/component.ts +1843 -0
- package/src/modes/components/status-line/context-thresholds.ts +86 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/index.ts +5 -0
- package/src/modes/components/status-line/presets.ts +106 -0
- package/src/modes/components/status-line/segments.ts +710 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/types.ts +159 -0
- package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
- package/src/modes/components/theme-selector.ts +68 -0
- package/src/modes/components/thinking-selector.ts +57 -0
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +26 -0
- package/src/modes/components/todo-reminder.ts +43 -0
- package/src/modes/components/tool-execution.ts +1467 -0
- package/src/modes/components/transcript-container.ts +527 -0
- package/src/modes/components/tree-selector.ts +1006 -0
- package/src/modes/components/ttsr-notification.ts +123 -0
- package/src/modes/components/usage-row.ts +52 -0
- package/src/modes/components/user-message-selector.ts +227 -0
- package/src/modes/components/user-message.ts +167 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +578 -0
- package/src/modes/controllers/btw-controller.ts +246 -0
- package/src/modes/controllers/command-controller-shared.ts +109 -0
- package/src/modes/controllers/command-controller.ts +2024 -0
- package/src/modes/controllers/event-controller.ts +2173 -0
- package/src/modes/controllers/extension-ui-controller.ts +1243 -0
- package/src/modes/controllers/input-controller.ts +2037 -0
- package/src/modes/controllers/live-command-controller.ts +261 -0
- package/src/modes/controllers/mcp-command-controller.ts +2552 -0
- package/src/modes/controllers/omfg-controller.ts +287 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +2058 -0
- package/src/modes/controllers/session-focus-controller.ts +117 -0
- package/src/modes/controllers/ssh-command-controller.ts +385 -0
- package/src/modes/controllers/streaming-reveal.ts +399 -0
- package/src/modes/controllers/tan-command-controller.ts +243 -0
- package/src/modes/controllers/todo-command-controller.ts +487 -0
- package/src/modes/controllers/tool-args-reveal.ts +591 -0
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/github-ref-autocomplete.ts +75 -0
- package/src/modes/gradient-highlight.ts +99 -0
- package/src/modes/image-references.ts +137 -0
- package/src/modes/index.ts +10 -0
- package/src/modes/interactive-mode.ts +5055 -0
- package/src/modes/internal-url-autocomplete.ts +158 -0
- package/src/modes/loop-limit.ts +192 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/magic-keywords.ts +42 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/oauth-manual-input.ts +69 -0
- package/src/modes/orchestrate.ts +43 -0
- package/src/modes/print-mode.ts +282 -0
- package/src/modes/prompt-action-autocomplete.ts +322 -0
- package/src/modes/queue-input.ts +132 -0
- package/src/modes/rpc/host-tools.ts +204 -0
- package/src/modes/rpc/host-uris.ts +241 -0
- package/src/modes/rpc/rpc-client.ts +1213 -0
- package/src/modes/rpc/rpc-frame.ts +316 -0
- package/src/modes/rpc/rpc-input.ts +38 -0
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +1519 -0
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +544 -0
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/runtime-init.ts +144 -0
- package/src/modes/session-observer-registry.ts +223 -0
- package/src/modes/session-teardown.ts +82 -0
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +103 -0
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
- package/src/modes/setup-wizard/scenes/model.ts +132 -0
- package/src/modes/setup-wizard/scenes/outro.ts +35 -0
- package/src/modes/setup-wizard/scenes/providers.ts +105 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
- package/src/modes/setup-wizard/scenes/splash.ts +201 -0
- package/src/modes/setup-wizard/scenes/theme.ts +330 -0
- package/src/modes/setup-wizard/scenes/types.ts +65 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
- package/src/modes/shared.ts +49 -0
- package/src/modes/skill-command.ts +91 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-poimandres.json +143 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +199 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-poimandres.json +143 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +92 -0
- package/src/modes/theme/shimmer.ts +305 -0
- package/src/modes/theme/theme-schema.json +463 -0
- package/src/modes/theme/theme.ts +3171 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +494 -0
- package/src/modes/ultrathink.ts +42 -0
- package/src/modes/utils/context-usage.ts +518 -0
- package/src/modes/utils/copy-targets.ts +378 -0
- package/src/modes/utils/hotkeys-markdown.ts +65 -0
- package/src/modes/utils/interactive-context-helpers.ts +31 -0
- package/src/modes/utils/keybinding-matchers.ts +86 -0
- package/src/modes/utils/tools-markdown.ts +31 -0
- package/src/modes/utils/transcript-render-helpers.ts +253 -0
- package/src/modes/utils/ui-helpers.ts +976 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/modes/workflow.ts +55 -0
- package/src/plan-mode/approved-plan.ts +194 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/plan-mode/plan-handoff.ts +37 -0
- package/src/plan-mode/plan-protection.ts +31 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/priority.json +60 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/advise-tool.md +3 -0
- package/src/prompts/advisor/context-files.md +8 -0
- package/src/prompts/advisor/system.md +98 -0
- package/src/prompts/agents/designer.md +74 -0
- package/src/prompts/agents/frontmatter.md +12 -0
- package/src/prompts/agents/init.md +33 -0
- package/src/prompts/agents/librarian.md +119 -0
- package/src/prompts/agents/reviewer.md +139 -0
- package/src/prompts/agents/scout.md +58 -0
- package/src/prompts/agents/security-reviewer.md +75 -0
- package/src/prompts/agents/task.md +17 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/bench.md +6 -0
- package/src/prompts/ci-green-request.md +36 -0
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/goals/goal-mode-context.md +4 -0
- package/src/prompts/goals/goal-todo-context.md +12 -0
- package/src/prompts/goals/guided-goal-interview.md +43 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/consolidation_system.md +4 -0
- package/src/prompts/memories/read-path.md +17 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-custom-request.md +21 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +68 -0
- package/src/prompts/security/scan-coordinator.md +7 -0
- package/src/prompts/security/scan-request.md +21 -0
- package/src/prompts/security/validate-request.md +8 -0
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/prompts/skills/autoload.md +8 -0
- package/src/prompts/skills/user-invocation.md +11 -0
- package/src/prompts/steering/parent-irc.md +5 -0
- package/src/prompts/steering/user-interjection.md +6 -0
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/agent-creation-architect.md +50 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-continue.md +1 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +14 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +14 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +64 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +18 -0
- package/src/prompts/system/empty-stop-retry.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +9 -0
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/mcp-xdev-guidance.md +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/system/mid-run-todo-nudge.md +3 -0
- package/src/prompts/system/omfg-user.md +50 -0
- package/src/prompts/system/orchestrate-notice.md +40 -0
- package/src/prompts/system/personalities/default.md +18 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/plan-mode-active.md +125 -0
- package/src/prompts/system/plan-mode-approved.md +22 -0
- package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
- package/src/prompts/system/plan-mode-reference.md +10 -0
- package/src/prompts/system/plan-mode-subagent.md +33 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/plan-yolo-handoff.md +5 -0
- package/src/prompts/system/prewalk-checklist.md +7 -0
- package/src/prompts/system/prewalk-continue.md +1 -0
- package/src/prompts/system/prewalk-plan.md +13 -0
- package/src/prompts/system/project-prompt.md +61 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/resolve-device-reminder.md +3 -0
- package/src/prompts/system/rewind-report.md +6 -0
- package/src/prompts/system/side-channel-no-tools.md +3 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/speech-rewrite.md +15 -0
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/subagent-system-prompt.md +73 -0
- package/src/prompts/system/subagent-user-prompt.md +3 -0
- package/src/prompts/system/subagent-yield-reminder.md +23 -0
- package/src/prompts/system/system-prompt.md +263 -0
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/system/task-label.md +23 -0
- package/src/prompts/system/thinking-loop-redirect.md +10 -0
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system.md +16 -0
- package/src/prompts/system/tool-call-loop-redirect.md +8 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/system/ultrathink-notice.md +3 -0
- package/src/prompts/system/unexpected-stop-classifier.md +17 -0
- package/src/prompts/system/unexpected-stop-retry.md +4 -0
- package/src/prompts/system/vibe-mode-active.md +26 -0
- package/src/prompts/system/web-search.md +25 -0
- package/src/prompts/system/workflow-notice.md +112 -0
- package/src/prompts/system/xdev-mount-notice.md +20 -0
- package/src/prompts/tools/apply-patch.md +65 -0
- package/src/prompts/tools/ask.md +22 -0
- package/src/prompts/tools/ast-edit.md +11 -0
- package/src/prompts/tools/ast-grep.md +19 -0
- package/src/prompts/tools/async-result.md +8 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/browser.md +29 -0
- package/src/prompts/tools/checkpoint.md +15 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/debug.md +3 -0
- package/src/prompts/tools/eval.md +45 -0
- package/src/prompts/tools/github.md +22 -0
- package/src/prompts/tools/glob.md +16 -0
- package/src/prompts/tools/goal.md +11 -0
- package/src/prompts/tools/grep.md +13 -0
- package/src/prompts/tools/hub.md +34 -0
- package/src/prompts/tools/image-attachment-describe-system.md +8 -0
- package/src/prompts/tools/image-attachment-describe.md +10 -0
- package/src/prompts/tools/image-gen.md +7 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +22 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/lsp.md +19 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/memory-edit.md +12 -0
- package/src/prompts/tools/patch.md +57 -0
- package/src/prompts/tools/read.md +27 -0
- package/src/prompts/tools/recall.md +7 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/replace.md +30 -0
- package/src/prompts/tools/retain.md +6 -0
- package/src/prompts/tools/rewind.md +14 -0
- package/src/prompts/tools/security-publish.md +1 -0
- package/src/prompts/tools/security-scan.md +1 -0
- package/src/prompts/tools/task-async-contract.md +1 -0
- package/src/prompts/tools/task-summary.md +20 -0
- package/src/prompts/tools/task.md +83 -0
- package/src/prompts/tools/todo.md +42 -0
- package/src/prompts/tools/vibe-kill.md +3 -0
- package/src/prompts/tools/vibe-list.md +3 -0
- package/src/prompts/tools/vibe-send.md +9 -0
- package/src/prompts/tools/vibe-spawn.md +10 -0
- package/src/prompts/tools/vibe-turn-result.md +19 -0
- package/src/prompts/tools/vibe-wait.md +8 -0
- package/src/prompts/tools/web-search.md +8 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/registry/agent-lifecycle.ts +505 -0
- package/src/registry/agent-registry.ts +287 -0
- package/src/registry/persisted-agents.ts +423 -0
- package/src/sdk.ts +3820 -0
- package/src/secrets/index.ts +375 -0
- package/src/secrets/obfuscator.ts +2629 -0
- package/src/secrets/regex.ts +21 -0
- package/src/security/auth.ts +98 -0
- package/src/security/cloud.ts +686 -0
- package/src/security/comparison.ts +255 -0
- package/src/security/contracts/ids.ts +111 -0
- package/src/security/contracts/index.ts +4 -0
- package/src/security/contracts/schemas.ts +219 -0
- package/src/security/contracts/types.ts +254 -0
- package/src/security/contracts/validation.ts +69 -0
- package/src/security/coordinator.ts +708 -0
- package/src/security/importers/codex-security.ts +387 -0
- package/src/security/importers/index.ts +2 -0
- package/src/security/importers/sarif.ts +357 -0
- package/src/security/index.ts +13 -0
- package/src/security/preflight.ts +405 -0
- package/src/security/provenance.ts +106 -0
- package/src/security/publication.ts +326 -0
- package/src/security/remediation.ts +93 -0
- package/src/security/resource-output.ts +50 -0
- package/src/security/sarif.ts +78 -0
- package/src/security/store.ts +430 -0
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-events.ts +67 -0
- package/src/session/agent-session-types.ts +407 -0
- package/src/session/agent-session.ts +9000 -0
- package/src/session/agent-storage.ts +807 -0
- package/src/session/artifacts.ts +154 -0
- package/src/session/async-job-delivery.ts +82 -0
- package/src/session/auth-broker-config.ts +92 -0
- package/src/session/auth-storage.ts +25 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.ts +295 -0
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/client-bridge.ts +85 -0
- package/src/session/codex-auto-reset.ts +673 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/compact-modes.ts +105 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/eval-runner.ts +219 -0
- package/src/session/exit-diagnostics.ts +310 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/history-storage.ts +329 -0
- package/src/session/indexed-session-storage.ts +553 -0
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/launch-completion.ts +37 -0
- package/src/session/messages.ts +1304 -0
- package/src/session/model-controls.ts +757 -0
- package/src/session/prewalk.ts +279 -0
- package/src/session/provider-image-budget.ts +86 -0
- package/src/session/queued-messages.ts +99 -0
- package/src/session/redis-session-storage.ts +257 -0
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1896 -0
- package/src/session/session-context.ts +584 -0
- package/src/session/session-dump-format.ts +216 -0
- package/src/session/session-entries.ts +306 -0
- package/src/session/session-handoff.ts +325 -0
- package/src/session/session-history-format.ts +459 -0
- package/src/session/session-listing.ts +715 -0
- package/src/session/session-loader.ts +362 -0
- package/src/session/session-maintenance.ts +3115 -0
- package/src/session/session-manager.ts +2747 -0
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-metadata.ts +53 -0
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +280 -0
- package/src/session/session-persistence.ts +293 -0
- package/src/session/session-provider-boundary.ts +306 -0
- package/src/session/session-stats.ts +349 -0
- package/src/session/session-storage.ts +774 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/session-tools.ts +1385 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/settings-stream-fn.ts +79 -0
- package/src/session/shake-types.ts +43 -0
- package/src/session/snapcompact-inline.ts +545 -0
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/sql-session-storage.ts +374 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +1459 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/tool-choice-queue.ts +305 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-persistence.ts +142 -0
- package/src/session/turn-recovery.ts +2005 -0
- package/src/session/unexpected-stop-classifier.ts +141 -0
- package/src/session/yield-queue.ts +288 -0
- package/src/slash-commands/acp-builtins.ts +70 -0
- package/src/slash-commands/available-commands.ts +105 -0
- package/src/slash-commands/builtin-registry.ts +3149 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
- package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
- package/src/slash-commands/helpers/context-report.ts +66 -0
- package/src/slash-commands/helpers/format.ts +46 -0
- package/src/slash-commands/helpers/logout.ts +108 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +533 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/reset-usage.ts +68 -0
- package/src/slash-commands/helpers/security.ts +451 -0
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/ssh.ts +196 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
- package/src/slash-commands/helpers/todo.ts +285 -0
- package/src/slash-commands/helpers/usage-report.ts +198 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/types.ts +139 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +667 -0
- package/src/ssh/file-transfer.ts +214 -0
- package/src/ssh/sshfs-mount.ts +163 -0
- package/src/ssh/utils.ts +51 -0
- package/src/startup-splash.ts +19 -0
- package/src/stt/asr-client.ts +401 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +603 -0
- package/src/stt/downloader.ts +142 -0
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +6 -0
- package/src/stt/models.ts +150 -0
- package/src/stt/sherpa-runtime.ts +71 -0
- package/src/stt/stt-controller.ts +320 -0
- package/src/stt/submit-trigger.ts +74 -0
- package/src/subprocess/worker-client.ts +463 -0
- package/src/subprocess/worker-runtime.ts +494 -0
- package/src/system-prompt.ts +921 -0
- package/src/task/agents.ts +170 -0
- package/src/task/commands.ts +132 -0
- package/src/task/discovery.ts +145 -0
- package/src/task/executor.ts +3431 -0
- package/src/task/index.ts +1515 -0
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/isolation-runner.ts +444 -0
- package/src/task/label.ts +40 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/omp-command.ts +26 -0
- package/src/task/output-manager.ts +115 -0
- package/src/task/parallel.ts +221 -0
- package/src/task/persisted-revive.ts +173 -0
- package/src/task/prewalk.ts +6 -0
- package/src/task/prompt-policy.ts +8 -0
- package/src/task/provider-concurrency.ts +100 -0
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/render.ts +1830 -0
- package/src/task/renderer.ts +14 -0
- package/src/task/repair-args.ts +118 -0
- package/src/task/spawn-policy.ts +72 -0
- package/src/task/structured-subagent.ts +676 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/types.ts +555 -0
- package/src/task/worktree.ts +967 -0
- package/src/task/yield-assembly.ts +198 -0
- package/src/telemetry-export.ts +504 -0
- package/src/thinking.ts +377 -0
- package/src/tiny/device.ts +111 -0
- package/src/tiny/dtype.ts +101 -0
- package/src/tiny/message-preproc.ts +155 -0
- package/src/tiny/models.ts +268 -0
- package/src/tiny/text.ts +290 -0
- package/src/tiny/title-client.ts +460 -0
- package/src/tiny/title-protocol.ts +56 -0
- package/src/tiny/worker.ts +354 -0
- package/src/tools/acp-bridge.ts +125 -0
- package/src/tools/approval.ts +245 -0
- package/src/tools/ask.ts +1459 -0
- package/src/tools/ast-edit.ts +718 -0
- package/src/tools/ast-grep.ts +526 -0
- package/src/tools/auto-generated-guard.ts +335 -0
- package/src/tools/bash-interactive.ts +435 -0
- package/src/tools/bash-interceptor.ts +148 -0
- package/src/tools/bash-pty-selection.ts +14 -0
- package/src/tools/bash-skill-urls.ts +350 -0
- package/src/tools/bash.ts +1826 -0
- package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
- package/src/tools/browser/aria/aria-snapshot.ts +131 -0
- package/src/tools/browser/attach.ts +219 -0
- package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
- package/src/tools/browser/cmux/rpc.ts +200 -0
- package/src/tools/browser/cmux/socket-client.ts +445 -0
- package/src/tools/browser/launch.ts +939 -0
- package/src/tools/browser/readable.ts +112 -0
- package/src/tools/browser/registry.ts +447 -0
- package/src/tools/browser/relay/bridge.ts +945 -0
- package/src/tools/browser/relay/daemon.ts +117 -0
- package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
- package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
- package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
- package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
- package/src/tools/browser/relay/kind.ts +41 -0
- package/src/tools/browser/relay/protocol.ts +54 -0
- package/src/tools/browser/relay/server.ts +141 -0
- package/src/tools/browser/render.ts +229 -0
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/shared-daemon.ts +139 -0
- package/src/tools/browser/tab-protocol.ts +123 -0
- package/src/tools/browser/tab-supervisor.ts +1077 -0
- package/src/tools/browser/tab-worker-entry.ts +29 -0
- package/src/tools/browser/tab-worker.ts +1982 -0
- package/src/tools/browser.ts +487 -0
- package/src/tools/builtin-names.ts +67 -0
- package/src/tools/checkpoint.ts +137 -0
- package/src/tools/computer/exposure.ts +14 -0
- package/src/tools/computer/protocol.ts +69 -0
- package/src/tools/computer/supervisor.ts +409 -0
- package/src/tools/computer/worker-entry.ts +39 -0
- package/src/tools/computer/worker.ts +745 -0
- package/src/tools/computer-renderer.ts +147 -0
- package/src/tools/computer.ts +222 -0
- package/src/tools/conflict-detect.ts +815 -0
- package/src/tools/context.ts +49 -0
- package/src/tools/debug.ts +1121 -0
- package/src/tools/default-renderer.ts +154 -0
- package/src/tools/essential-tools.ts +46 -0
- package/src/tools/eval-backends.ts +34 -0
- package/src/tools/eval-format/index.ts +24 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/julia.ts +446 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval-format/ruby.ts +380 -0
- package/src/tools/eval-render.ts +784 -0
- package/src/tools/eval.ts +774 -0
- package/src/tools/fetch.ts +1889 -0
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gh-cache-invalidation.ts +175 -0
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +484 -0
- package/src/tools/gh.ts +3958 -0
- package/src/tools/github-cache.ts +663 -0
- package/src/tools/glob.ts +691 -0
- package/src/tools/grep.ts +1920 -0
- package/src/tools/grouped-file-output.ts +210 -0
- package/src/tools/hub/index.ts +579 -0
- package/src/tools/hub/jobs.ts +714 -0
- package/src/tools/hub/launch.ts +690 -0
- package/src/tools/hub/messaging.ts +735 -0
- package/src/tools/hub/types.ts +117 -0
- package/src/tools/image-gen.ts +1689 -0
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +736 -0
- package/src/tools/inspect-image-renderer.ts +133 -0
- package/src/tools/inspect-image.ts +316 -0
- package/src/tools/json-tree.ts +260 -0
- package/src/tools/jtd-to-json-schema.ts +219 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/learn.ts +141 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/manage-skill.ts +102 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/memory-edit.ts +61 -0
- package/src/tools/memory-recall.ts +102 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +211 -0
- package/src/tools/memory-retain.ts +89 -0
- package/src/tools/output-meta.ts +860 -0
- package/src/tools/output-schema-validator.ts +307 -0
- package/src/tools/path-utils.ts +1489 -0
- package/src/tools/plan-mode-guard.ts +155 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
- package/src/tools/read.ts +3762 -0
- package/src/tools/render-utils.ts +926 -0
- package/src/tools/renderers.ts +133 -0
- package/src/tools/report-tool-issue.ts +568 -0
- package/src/tools/resolve.ts +423 -0
- package/src/tools/review.ts +103 -0
- package/src/tools/run-scope.ts +417 -0
- package/src/tools/security-scan.ts +287 -0
- package/src/tools/shell-tokenize.ts +213 -0
- package/src/tools/sqlite-reader.ts +884 -0
- package/src/tools/terminal-output.ts +141 -0
- package/src/tools/todo.ts +1226 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +102 -0
- package/src/tools/tool-timeouts.ts +38 -0
- package/src/tools/tts.ts +266 -0
- package/src/tools/vibe.ts +608 -0
- package/src/tools/write.ts +1646 -0
- package/src/tools/xdev.ts +560 -0
- package/src/tools/yield.ts +486 -0
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +10 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/speakable.ts +392 -0
- package/src/tts/speech-enhancer.ts +206 -0
- package/src/tts/streaming-player.ts +120 -0
- package/src/tts/tts-client.ts +475 -0
- package/src/tts/tts-protocol.ts +69 -0
- package/src/tts/tts-worker.ts +434 -0
- package/src/tts/vocalizer.ts +419 -0
- package/src/tts/wav.ts +58 -0
- package/src/tui/code-cell.ts +268 -0
- package/src/tui/file-list.ts +55 -0
- package/src/tui/hyperlink.ts +178 -0
- package/src/tui/index.ts +13 -0
- package/src/tui/output-block.ts +268 -0
- package/src/tui/status-line.ts +54 -0
- package/src/tui/tree-list.ts +172 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/tui/width-aware-text.ts +58 -0
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/block-context.ts +312 -0
- package/src/utils/changelog.ts +373 -0
- package/src/utils/clipboard.ts +361 -0
- package/src/utils/command-args.ts +74 -0
- package/src/utils/commit-message-generator.ts +148 -0
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/edit-mode.ts +61 -0
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +80 -0
- package/src/utils/fetch-timeout.ts +10 -0
- package/src/utils/file-display-mode.ts +44 -0
- package/src/utils/file-mentions.ts +293 -0
- package/src/utils/git.ts +2472 -0
- package/src/utils/image-loading.ts +236 -0
- package/src/utils/image-resize.ts +420 -0
- package/src/utils/image-vision-fallback.ts +196 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/ipc.ts +38 -0
- package/src/utils/jj.ts +422 -0
- package/src/utils/lang-from-path.ts +251 -0
- package/src/utils/late-cleanup.ts +17 -0
- package/src/utils/local-date.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +223 -0
- package/src/utils/mupdf-wasm-embed.ts +12 -0
- package/src/utils/open.ts +126 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/qrcode.ts +535 -0
- package/src/utils/sample-profile.ts +437 -0
- package/src/utils/session-color.ts +142 -0
- package/src/utils/shell-snapshot-fn-env.sh +63 -0
- package/src/utils/shell-snapshot.ts +326 -0
- package/src/utils/sixel.ts +69 -0
- package/src/utils/thinking-display.ts +163 -0
- package/src/utils/title-generator.ts +597 -0
- package/src/utils/token-rate.ts +72 -0
- package/src/utils/tool-choice.ts +50 -0
- package/src/utils/tools-manager.ts +411 -0
- package/src/utils/turndown.ts +82 -0
- package/src/utils/zip.ts +1106 -0
- package/src/vibe/runtime.ts +1684 -0
- package/src/vibe/state.ts +4 -0
- package/src/web/kagi.ts +305 -0
- package/src/web/parallel.ts +354 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +97 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +663 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +800 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +129 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +173 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +121 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +189 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +94 -0
- package/src/web/scrapers/types.ts +354 -0
- package/src/web/scrapers/utils.ts +109 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +325 -0
- package/src/web/search/index.ts +393 -0
- package/src/web/search/provider.ts +272 -0
- package/src/web/search/providers/anthropic.ts +406 -0
- package/src/web/search/providers/base.ts +112 -0
- package/src/web/search/providers/brave.ts +181 -0
- package/src/web/search/providers/browser-headers.ts +82 -0
- package/src/web/search/providers/browser-page.ts +125 -0
- package/src/web/search/providers/codex.ts +788 -0
- package/src/web/search/providers/duckduckgo.ts +382 -0
- package/src/web/search/providers/ecosia.ts +183 -0
- package/src/web/search/providers/exa.ts +513 -0
- package/src/web/search/providers/firecrawl.ts +213 -0
- package/src/web/search/providers/gemini.ts +634 -0
- package/src/web/search/providers/google.ts +195 -0
- package/src/web/search/providers/jina.ts +144 -0
- package/src/web/search/providers/kagi.ts +98 -0
- package/src/web/search/providers/kimi.ts +221 -0
- package/src/web/search/providers/mojeek.ts +220 -0
- package/src/web/search/providers/parallel.ts +186 -0
- package/src/web/search/providers/perplexity-auth.ts +142 -0
- package/src/web/search/providers/perplexity.ts +997 -0
- package/src/web/search/providers/public.ts +199 -0
- package/src/web/search/providers/searxng.ts +467 -0
- package/src/web/search/providers/startpage.ts +225 -0
- package/src/web/search/providers/synthetic.ts +126 -0
- package/src/web/search/providers/tavily.ts +244 -0
- package/src/web/search/providers/tinyfish.ts +166 -0
- package/src/web/search/providers/utils.ts +130 -0
- package/src/web/search/providers/xai.ts +372 -0
- package/src/web/search/providers/zai.ts +452 -0
- package/src/web/search/query.ts +850 -0
- package/src/web/search/render.ts +262 -0
- package/src/web/search/types.ts +518 -0
- package/src/web/search/utils.ts +17 -0
- package/src/workspace-tree.ts +326 -0
|
@@ -0,0 +1,2005 @@
|
|
|
1
|
+
import { scheduler } from "node:timers/promises";
|
|
2
|
+
import {
|
|
3
|
+
type Agent,
|
|
4
|
+
AgentBusyError,
|
|
5
|
+
type AgentMessage,
|
|
6
|
+
isSyntheticToolResultMessage,
|
|
7
|
+
type ThinkingLevel,
|
|
8
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type {
|
|
10
|
+
AssistantMessage,
|
|
11
|
+
AssistantRetryRecovery,
|
|
12
|
+
AssistantRetryRecoveryKind,
|
|
13
|
+
CodexCompactionContext,
|
|
14
|
+
Effort,
|
|
15
|
+
Model,
|
|
16
|
+
ModelUsageHealth,
|
|
17
|
+
TextContent,
|
|
18
|
+
ThinkingContent,
|
|
19
|
+
ToolChoice,
|
|
20
|
+
} from "@oh-my-pi/pi-ai";
|
|
21
|
+
import { calculateRateLimitBackoffMs, parseRateLimitReason } from "@oh-my-pi/pi-ai";
|
|
22
|
+
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
23
|
+
import { kCursorExecResolved } from "@oh-my-pi/pi-ai/utils/block-symbols";
|
|
24
|
+
import { isFireworksFastModelId, toFireworksBaseModelId } from "@oh-my-pi/pi-catalog/fireworks-model-id";
|
|
25
|
+
import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
|
26
|
+
import { extractRetryHint, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
27
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
28
|
+
import { formatModelStringWithRouting, resolveModelOverride } from "../config/model-resolver";
|
|
29
|
+
|
|
30
|
+
import type { Settings } from "../config/settings";
|
|
31
|
+
import type { RecoveredRetryError } from "../extensibility/shared-events";
|
|
32
|
+
import emptyStopRetryTemplate from "../prompts/system/empty-stop-retry.md" with { type: "text" };
|
|
33
|
+
import thinkingLoopRedirectTemplate from "../prompts/system/thinking-loop-redirect.md" with { type: "text" };
|
|
34
|
+
import unexpectedStopRetryTemplate from "../prompts/system/unexpected-stop-retry.md" with { type: "text" };
|
|
35
|
+
import {
|
|
36
|
+
AUTO_THINKING,
|
|
37
|
+
type ConfiguredThinkingLevel,
|
|
38
|
+
clampThinkingLevelToCeiling,
|
|
39
|
+
modelSupportsEffortCeiling,
|
|
40
|
+
} from "../thinking";
|
|
41
|
+
import type { AgentSessionEvent } from "./agent-session-events";
|
|
42
|
+
import type {
|
|
43
|
+
InitialRetryFallbackState,
|
|
44
|
+
UsageFallbackConfirmation,
|
|
45
|
+
UsageFallbackConfirmer,
|
|
46
|
+
} from "./agent-session-types";
|
|
47
|
+
import { isEmptyErrorTurn } from "./messages";
|
|
48
|
+
import {
|
|
49
|
+
type ActiveRetryFallbackState,
|
|
50
|
+
calculateRetryBackoffDelayMs,
|
|
51
|
+
findRetryFallbackCandidates,
|
|
52
|
+
formatRetryFallbackSelector,
|
|
53
|
+
getRetryFallbackChains,
|
|
54
|
+
getRetryFallbackRevertPolicy,
|
|
55
|
+
parseRetryFallbackSelector,
|
|
56
|
+
type RetryFallbackChains,
|
|
57
|
+
type RetryFallbackResolutionContext,
|
|
58
|
+
type RetryFallbackRevertPolicy,
|
|
59
|
+
type RetryFallbackSelector,
|
|
60
|
+
resolveRetryFallbackChainKey,
|
|
61
|
+
validateRetryFallbackChains,
|
|
62
|
+
} from "./retry-fallback-chains";
|
|
63
|
+
import { getLatestCompactionEntry } from "./session-context";
|
|
64
|
+
import { EPHEMERAL_MODEL_CHANGE_ROLE, type SessionEntry } from "./session-entries";
|
|
65
|
+
import type { SessionManager } from "./session-manager";
|
|
66
|
+
import { sameMessageContent, sessionMessagePersistenceKey } from "./turn-persistence";
|
|
67
|
+
import { classifyUnexpectedStop, isUnexpectedStopCandidate } from "./unexpected-stop-classifier";
|
|
68
|
+
|
|
69
|
+
const THINKING_LOOP_REDIRECT_TYPE = "thinking-loop-redirect";
|
|
70
|
+
const UNEXPECTED_STOP_MAX_RETRIES = 3;
|
|
71
|
+
const UNEXPECTED_STOP_TIMEOUT_MS = 4000;
|
|
72
|
+
const EMPTY_STOP_MAX_RETRIES = 3;
|
|
73
|
+
const SIBLING_UNBLOCK_BUFFER_MS = 1_000;
|
|
74
|
+
const NON_WHITESPACE_RE = /\S/;
|
|
75
|
+
const USAGE_PREFLIGHT_BLOCKED_PREFIX = "Usage preflight blocked:";
|
|
76
|
+
|
|
77
|
+
function hasNonWhitespace(value: string): boolean {
|
|
78
|
+
return NON_WHITESPACE_RE.test(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function syntheticToolResultTailStart(messages: readonly AgentMessage[]): number {
|
|
82
|
+
let index = messages.length;
|
|
83
|
+
while (index > 0 && isSyntheticToolResultMessage(messages[index - 1])) {
|
|
84
|
+
index--;
|
|
85
|
+
}
|
|
86
|
+
return index;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function retryableAssistantTurnEnd(messages: readonly AgentMessage[]): number | undefined {
|
|
90
|
+
const turnEnd = syntheticToolResultTailStart(messages);
|
|
91
|
+
const message = messages[turnEnd - 1];
|
|
92
|
+
if (message?.role !== "assistant") return undefined;
|
|
93
|
+
if (message.stopReason !== "error" && message.stopReason !== "aborted") return undefined;
|
|
94
|
+
return turnEnd;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Result shape shared with automatic maintenance recovery. */
|
|
98
|
+
export interface RecoveryCompactionResult {
|
|
99
|
+
deferredHandoff: boolean;
|
|
100
|
+
continuationScheduled: boolean;
|
|
101
|
+
automaticContinuationBlocked?: boolean;
|
|
102
|
+
historyRewritten?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Capabilities borrowed from the owning AgentSession. */
|
|
106
|
+
export interface TurnRecoveryHost {
|
|
107
|
+
agent: Agent;
|
|
108
|
+
sessionManager: SessionManager;
|
|
109
|
+
settings: Settings;
|
|
110
|
+
modelRegistry: ModelRegistry;
|
|
111
|
+
configWarnings: string[];
|
|
112
|
+
model(): Model | undefined;
|
|
113
|
+
/** Whether streamed text has already been committed to the active output sink. */
|
|
114
|
+
textOutputCommitted(): boolean;
|
|
115
|
+
thinkingLevel(): ThinkingLevel | undefined;
|
|
116
|
+
configuredThinkingLevel(): ConfiguredThinkingLevel | undefined;
|
|
117
|
+
setThinkingLevel(level: ConfiguredThinkingLevel | undefined): void;
|
|
118
|
+
/** Hard per-session effort ceiling; fallback recovery must never raise thinking above it. */
|
|
119
|
+
thinkingLevelCeiling(): Effort | undefined;
|
|
120
|
+
isDisposed(): boolean;
|
|
121
|
+
isStreaming(): boolean;
|
|
122
|
+
isCompacting(): boolean;
|
|
123
|
+
abortInProgress(): boolean;
|
|
124
|
+
streamingEditAbortTriggered(): boolean;
|
|
125
|
+
promptGeneration(): number;
|
|
126
|
+
sessionId(): string;
|
|
127
|
+
emitSessionEvent(event: AgentSessionEvent): Promise<void>;
|
|
128
|
+
scheduleAgentContinue(options: { delayMs?: number; generation?: number; onError?: (error: unknown) => void }): void;
|
|
129
|
+
waitForSessionMessagePersistence(message: AssistantMessage): Promise<void>;
|
|
130
|
+
appendSessionMessage(message: AssistantMessage): void;
|
|
131
|
+
persistedAssistantEntryId(message: AssistantMessage): string | undefined;
|
|
132
|
+
sessionMessageAlreadyPersisted(message: AssistantMessage): boolean;
|
|
133
|
+
setModelWithProviderSessionReset(model: Model): Promise<void>;
|
|
134
|
+
resetCurrentResponsesProviderSession(reason: string): void;
|
|
135
|
+
/**
|
|
136
|
+
* Spend a saved Codex reset for the blocked pool, if eligible.
|
|
137
|
+
* `activeBlockUnblockAtMs` is the absolute unblock time parsed from the
|
|
138
|
+
* live usage-limit error — authoritative for the active account when the
|
|
139
|
+
* usage report still shows a pre-block snapshot.
|
|
140
|
+
*/
|
|
141
|
+
maybeAutoRedeemCodexReset(activeBlockUnblockAtMs?: number): Promise<boolean>;
|
|
142
|
+
runAutoCompaction(
|
|
143
|
+
reason: "overflow" | "threshold" | "idle" | "incomplete",
|
|
144
|
+
willRetry: boolean,
|
|
145
|
+
deferred?: boolean,
|
|
146
|
+
allowDefer?: boolean,
|
|
147
|
+
options?: {
|
|
148
|
+
autoContinue?: boolean;
|
|
149
|
+
triggerContextTokens?: number;
|
|
150
|
+
suppressContinuation?: boolean;
|
|
151
|
+
suppressHandoff?: boolean;
|
|
152
|
+
phase?: CodexCompactionContext["phase"];
|
|
153
|
+
terminalTextAnswer?: boolean;
|
|
154
|
+
},
|
|
155
|
+
): Promise<RecoveryCompactionResult>;
|
|
156
|
+
withBashBranchTransition<T>(operation: () => T): T;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Construction-time retry state restored from model selection. */
|
|
160
|
+
export interface TurnRecoveryOptions {
|
|
161
|
+
initialRetryFallback?: InitialRetryFallbackState;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
type PendingRecoveredRetryError = {
|
|
165
|
+
entryId: string;
|
|
166
|
+
persistenceKey: string;
|
|
167
|
+
recovery: AssistantRetryRecoveryKind;
|
|
168
|
+
attempt: number;
|
|
169
|
+
note: string;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
type UsageLimitOutcome = {
|
|
173
|
+
switchedCredential: boolean;
|
|
174
|
+
retryAfterMs: number;
|
|
175
|
+
retryAtMs: number | undefined;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** Owns terminal-stop recovery, automatic retries, and fallback routing. */
|
|
179
|
+
export class TurnRecovery {
|
|
180
|
+
readonly #host: TurnRecoveryHost;
|
|
181
|
+
#retryAbortController: AbortController | undefined;
|
|
182
|
+
#retryAttempt = 0;
|
|
183
|
+
#retryPromise: Promise<void> | undefined;
|
|
184
|
+
#retryResolve: (() => void) | undefined;
|
|
185
|
+
#activeRetryFallback: ActiveRetryFallbackState | undefined;
|
|
186
|
+
#usageReserveApprovedSelector: string | undefined;
|
|
187
|
+
#pendingRecoveredRetryErrors: PendingRecoveredRetryError[] = [];
|
|
188
|
+
#usageLimitOutcomes = new WeakMap<AssistantMessage, Promise<UsageLimitOutcome>>();
|
|
189
|
+
#emptyStopRetryCount = 0;
|
|
190
|
+
#unexpectedStopRetryCount = 0;
|
|
191
|
+
#acceptTerminalEmptyStopForPrompt = false;
|
|
192
|
+
|
|
193
|
+
constructor(host: TurnRecoveryHost, options: TurnRecoveryOptions = {}) {
|
|
194
|
+
this.#host = host;
|
|
195
|
+
if (options.initialRetryFallback) {
|
|
196
|
+
this.#activeRetryFallback = {
|
|
197
|
+
...options.initialRetryFallback,
|
|
198
|
+
lastAppliedFallbackThinkingLevel: host.configuredThinkingLevel(),
|
|
199
|
+
pinned: options.initialRetryFallback.pinned ?? false,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
this.#validateRetryFallbackChains();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Current automatic retry attempt. */
|
|
206
|
+
get attempt(): number {
|
|
207
|
+
return this.#retryAttempt;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Promise settled when the active retry saga finishes. */
|
|
211
|
+
get retryPromise(): Promise<void> | undefined {
|
|
212
|
+
return this.#retryPromise;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Resolved selector while fallback routing owns the current model. */
|
|
216
|
+
get retryFallbackModel(): string | undefined {
|
|
217
|
+
const model = this.#host.model();
|
|
218
|
+
return this.#activeRetryFallback && model
|
|
219
|
+
? formatRetryFallbackSelector(model, this.#host.thinkingLevel())
|
|
220
|
+
: undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Resets per-prompt recovery counters and terminal-stop acceptance. */
|
|
224
|
+
resetForNewPrompt(): void {
|
|
225
|
+
this.#emptyStopRetryCount = 0;
|
|
226
|
+
this.#unexpectedStopRetryCount = 0;
|
|
227
|
+
this.#acceptTerminalEmptyStopForPrompt = false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Sets whether one terminal empty stop is accepted for the current prompt. */
|
|
231
|
+
setAcceptTerminalEmptyStop(accept: boolean): void {
|
|
232
|
+
this.#acceptTerminalEmptyStopForPrompt = accept;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Closes a successful retry saga and annotates recovered persisted errors. */
|
|
236
|
+
async onAssistantSettledSuccessfully(message: AssistantMessage): Promise<void> {
|
|
237
|
+
if (
|
|
238
|
+
message.stopReason === "error" ||
|
|
239
|
+
message.stopReason === "aborted" ||
|
|
240
|
+
this.#isEmptyAssistantStop(message) ||
|
|
241
|
+
this.#retryAttempt === 0
|
|
242
|
+
) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const model = this.#host.model();
|
|
246
|
+
if (this.#activeRetryFallback && model) {
|
|
247
|
+
await this.#host.emitSessionEvent({
|
|
248
|
+
type: "retry_fallback_succeeded",
|
|
249
|
+
model: formatRetryFallbackSelector(model, this.#host.thinkingLevel()),
|
|
250
|
+
role: this.#activeRetryFallback.role,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const recoveredErrors = await this.#markPendingRecoveredRetryErrors(message);
|
|
254
|
+
await this.#host.emitSessionEvent({
|
|
255
|
+
type: "auto_retry_end",
|
|
256
|
+
success: true,
|
|
257
|
+
attempt: this.#retryAttempt,
|
|
258
|
+
recoveredErrors,
|
|
259
|
+
});
|
|
260
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
261
|
+
this.#retryAttempt = 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Closes a failed retry saga when no compaction continuation took ownership. */
|
|
265
|
+
async onErrorSettledWithoutRetry(message: AssistantMessage, compaction: RecoveryCompactionResult): Promise<void> {
|
|
266
|
+
if (message.stopReason !== "error" || this.#retryAttempt === 0 || compaction.continuationScheduled) return;
|
|
267
|
+
const attempt = this.#retryAttempt;
|
|
268
|
+
this.#retryAttempt = 0;
|
|
269
|
+
await this.#host.emitSessionEvent({
|
|
270
|
+
type: "auto_retry_end",
|
|
271
|
+
success: false,
|
|
272
|
+
attempt,
|
|
273
|
+
finalError: message.errorMessage,
|
|
274
|
+
});
|
|
275
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Persists an otherwise skipped terminal empty error turn. */
|
|
279
|
+
persistTerminalEmptyErrorTurn(message: AssistantMessage): Promise<void> {
|
|
280
|
+
return this.#persistTerminalEmptyErrorTurn(message);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Handles empty terminal assistant turns and schedules bounded recovery. */
|
|
284
|
+
handleEmptyAssistantStop(message: AssistantMessage): Promise<boolean> {
|
|
285
|
+
return this.#handleEmptyAssistantStop(message);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Classifies suspicious terminal stops and schedules bounded recovery. */
|
|
289
|
+
handleUnexpectedAssistantStop(message: AssistantMessage): Promise<boolean> {
|
|
290
|
+
return this.#handleUnexpectedAssistantStop(message);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Removes a persisted failed assistant turn after its persistence slot settles. */
|
|
294
|
+
dropPersistedAssistantTurn(message: AssistantMessage): Promise<void> {
|
|
295
|
+
return this.#dropPersistedAssistantTurn(message);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Runs recovery compaction and restores the failed turn when no rewrite occurs. */
|
|
299
|
+
runRecoveryCompactionWithRollback(
|
|
300
|
+
reason: "overflow" | "incomplete",
|
|
301
|
+
message: AssistantMessage,
|
|
302
|
+
allowDefer: boolean,
|
|
303
|
+
options: { autoContinue: boolean; triggerContextTokens?: number },
|
|
304
|
+
): Promise<RecoveryCompactionResult> {
|
|
305
|
+
return this.#runRecoveryCompactionWithRollback(reason, message, allowDefer, options);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Restores the configured primary after fallback cooldown expiry. */
|
|
309
|
+
maybeRestoreRetryFallbackPrimary(): Promise<void> {
|
|
310
|
+
return this.#maybeRestoreRetryFallbackPrimary();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Applies model fallback policy from live usage health before a turn starts. */
|
|
314
|
+
maybeApplyUsageAwareFallback(signal: AbortSignal, confirmer?: UsageFallbackConfirmer): Promise<boolean> {
|
|
315
|
+
return this.#maybeApplyUsageAwareFallback(signal, confirmer);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Applies automatic retry, credential rotation, and model fallback policy. */
|
|
319
|
+
handleRetryableError(
|
|
320
|
+
message: AssistantMessage,
|
|
321
|
+
options?: {
|
|
322
|
+
allowModelFallback?: boolean;
|
|
323
|
+
fireworksFastFallback?: boolean;
|
|
324
|
+
hardErrorFallback?: boolean;
|
|
325
|
+
preserveFailedTurn?: boolean;
|
|
326
|
+
},
|
|
327
|
+
): Promise<boolean> {
|
|
328
|
+
return this.#handleRetryableError(message, options);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Records a usage-limit failure before replay eligibility decides whether the
|
|
333
|
+
* failed turn may be discarded. Returns whether credential recovery switched
|
|
334
|
+
* the active account.
|
|
335
|
+
*/
|
|
336
|
+
async recordUsageLimitOutcome(message: AssistantMessage): Promise<boolean> {
|
|
337
|
+
if (message.stopReason !== "error") return false;
|
|
338
|
+
const id = this.#classifyRetryMessage(message);
|
|
339
|
+
const activeModel = this.#host.model();
|
|
340
|
+
if (!activeModel || !AIError.is(id, AIError.Flag.UsageLimit)) return false;
|
|
341
|
+
|
|
342
|
+
let recorded = this.#usageLimitOutcomes.get(message);
|
|
343
|
+
if (!recorded) {
|
|
344
|
+
const errorMessage = message.errorMessage || "Unknown error";
|
|
345
|
+
const retryAfterMs =
|
|
346
|
+
this.#parseRetryAfterMsFromError(errorMessage) ??
|
|
347
|
+
calculateRateLimitBackoffMs(parseRateLimitReason(errorMessage));
|
|
348
|
+
recorded = (async (): Promise<UsageLimitOutcome> => {
|
|
349
|
+
const outcome = await this.#host.modelRegistry.authStorage.markUsageLimitReached(
|
|
350
|
+
activeModel.provider,
|
|
351
|
+
this.#host.sessionId(),
|
|
352
|
+
{ retryAfterMs, baseUrl: activeModel.baseUrl, modelId: activeModel.id },
|
|
353
|
+
);
|
|
354
|
+
return {
|
|
355
|
+
switchedCredential: outcome.switched,
|
|
356
|
+
retryAfterMs,
|
|
357
|
+
retryAtMs: outcome.retryAtMs,
|
|
358
|
+
};
|
|
359
|
+
})();
|
|
360
|
+
this.#usageLimitOutcomes.set(message, recorded);
|
|
361
|
+
}
|
|
362
|
+
return (await recorded).switchedCredential;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Prompts after transient overlap with a prior agent run. */
|
|
366
|
+
promptAgentWithIdleRetry(messages: AgentMessage[], options?: { toolChoice?: ToolChoice }): Promise<void> {
|
|
367
|
+
return this.#promptAgentWithIdleRetry(messages, options);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Parses provider retry and rate-limit reset hints into a delay. */
|
|
371
|
+
parseRetryAfterMsFromError(errorMessage: string): number | undefined {
|
|
372
|
+
return this.#parseRetryAfterMsFromError(errorMessage);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Resolve the pending retry promise */
|
|
376
|
+
resolveRetry(): void {
|
|
377
|
+
if (this.#retryResolve) {
|
|
378
|
+
this.#retryResolve();
|
|
379
|
+
this.#retryResolve = undefined;
|
|
380
|
+
this.#retryPromise = undefined;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#clearPendingRecoveredRetryErrors(): void {
|
|
385
|
+
this.#pendingRecoveredRetryErrors = [];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Durably record a terminal empty error turn (`stopReason: "error"` with no
|
|
390
|
+
* substantive content) that `#persistSessionMessageIfMissing` skipped, so the
|
|
391
|
+
* session JSONL keeps a record of why the run stopped instead of ending at the
|
|
392
|
+
* last tool result. A no-op for non-empty/non-error turns and idempotent via
|
|
393
|
+
* the already-persisted guard; the turn is dropped from active context by the
|
|
394
|
+
* caller (or `isProviderRefusalMessage`/`isEmptyErrorTurn` filters) so it is
|
|
395
|
+
* never replayed on the wire. Used by the retry-lifecycle dead-ends and the
|
|
396
|
+
* non-retry terminal error tail.
|
|
397
|
+
*/
|
|
398
|
+
async #persistTerminalEmptyErrorTurn(message: AssistantMessage): Promise<void> {
|
|
399
|
+
await this.#host.waitForSessionMessagePersistence(message);
|
|
400
|
+
if (!isEmptyErrorTurn(message)) return;
|
|
401
|
+
if (this.#host.sessionMessageAlreadyPersisted(message)) return;
|
|
402
|
+
this.#host.appendSessionMessage(message);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#retryRecoveryKind(
|
|
406
|
+
id: number,
|
|
407
|
+
switchedCredential: boolean,
|
|
408
|
+
switchedModel: boolean,
|
|
409
|
+
delayMs: number,
|
|
410
|
+
): AssistantRetryRecoveryKind {
|
|
411
|
+
if (switchedCredential) return "credential";
|
|
412
|
+
if (switchedModel) return "model";
|
|
413
|
+
if (AIError.is(id, AIError.Flag.UsageLimit) && delayMs > 0) return "wait";
|
|
414
|
+
return "plain";
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#retryRecoveryNote(recovery: AssistantRetryRecoveryKind, rateLimited: boolean): string {
|
|
418
|
+
const parts: string[] = [];
|
|
419
|
+
if (rateLimited) {
|
|
420
|
+
parts.push("rate-limited");
|
|
421
|
+
} else if (recovery === "plain") {
|
|
422
|
+
parts.push("error");
|
|
423
|
+
}
|
|
424
|
+
if (recovery === "credential") {
|
|
425
|
+
parts.push("switched account");
|
|
426
|
+
} else if (recovery === "model") {
|
|
427
|
+
parts.push("switched model");
|
|
428
|
+
} else if (recovery === "wait") {
|
|
429
|
+
parts.push("waited");
|
|
430
|
+
}
|
|
431
|
+
parts.push("retried");
|
|
432
|
+
return parts.join("; ");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async #recordPendingRecoveredRetryError(
|
|
436
|
+
message: AssistantMessage,
|
|
437
|
+
id: number,
|
|
438
|
+
options: { switchedCredential: boolean; switchedModel: boolean; delayMs: number },
|
|
439
|
+
): Promise<void> {
|
|
440
|
+
await this.persistTerminalEmptyErrorTurn(message);
|
|
441
|
+
const persistenceKey = sessionMessagePersistenceKey(message);
|
|
442
|
+
if (!persistenceKey) return;
|
|
443
|
+
let branchEntry: SessionEntry | undefined;
|
|
444
|
+
for (const entry of this.#host.sessionManager.getBranch().slice().reverse()) {
|
|
445
|
+
if (entry.type !== "message" || entry.message.role !== "assistant") continue;
|
|
446
|
+
if (sessionMessagePersistenceKey(entry.message) !== persistenceKey) continue;
|
|
447
|
+
if (!sameMessageContent(entry.message, message) && !this.#isSameAssistantMessage(entry.message, message)) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
branchEntry = entry;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
if (!branchEntry) return;
|
|
454
|
+
if (this.#pendingRecoveredRetryErrors.some(error => error.entryId === branchEntry.id)) return;
|
|
455
|
+
const rateLimited = AIError.is(id, AIError.Flag.UsageLimit);
|
|
456
|
+
const recovery = this.#retryRecoveryKind(id, options.switchedCredential, options.switchedModel, options.delayMs);
|
|
457
|
+
const note = this.#retryRecoveryNote(recovery, rateLimited);
|
|
458
|
+
this.#pendingRecoveredRetryErrors.push({
|
|
459
|
+
entryId: branchEntry.id,
|
|
460
|
+
persistenceKey,
|
|
461
|
+
recovery,
|
|
462
|
+
attempt: this.#retryAttempt,
|
|
463
|
+
note,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async #markPendingRecoveredRetryErrors(supersedingMessage: AssistantMessage): Promise<RecoveredRetryError[]> {
|
|
468
|
+
if (this.#pendingRecoveredRetryErrors.length === 0) return [];
|
|
469
|
+
const branch = this.#host.sessionManager.getBranch();
|
|
470
|
+
const branchById = new Map<string, SessionEntry>();
|
|
471
|
+
for (const entry of branch) {
|
|
472
|
+
branchById.set(entry.id, entry);
|
|
473
|
+
}
|
|
474
|
+
const recoveredAt = new Date().toISOString();
|
|
475
|
+
const supersededBy: AssistantRetryRecovery["supersededBy"] = {
|
|
476
|
+
timestamp: supersedingMessage.timestamp,
|
|
477
|
+
provider: supersedingMessage.provider,
|
|
478
|
+
model: supersedingMessage.model,
|
|
479
|
+
};
|
|
480
|
+
if (supersedingMessage.responseId) {
|
|
481
|
+
supersededBy.responseId = supersedingMessage.responseId;
|
|
482
|
+
}
|
|
483
|
+
const recoveredErrors: RecoveredRetryError[] = [];
|
|
484
|
+
for (const pending of this.#pendingRecoveredRetryErrors) {
|
|
485
|
+
let entry = branchById.get(pending.entryId);
|
|
486
|
+
if (entry?.type !== "message" || entry.message.role !== "assistant") {
|
|
487
|
+
entry = branch
|
|
488
|
+
.slice()
|
|
489
|
+
.reverse()
|
|
490
|
+
.find(
|
|
491
|
+
candidate =>
|
|
492
|
+
candidate.type === "message" &&
|
|
493
|
+
candidate.message.role === "assistant" &&
|
|
494
|
+
sessionMessagePersistenceKey(candidate.message) === pending.persistenceKey,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
if (entry?.type !== "message" || entry.message.role !== "assistant") continue;
|
|
498
|
+
const retryRecovery: AssistantRetryRecovery = {
|
|
499
|
+
kind: "auto-retry",
|
|
500
|
+
status: "recovered",
|
|
501
|
+
attempt: pending.attempt,
|
|
502
|
+
recoveredAt,
|
|
503
|
+
recovery: pending.recovery,
|
|
504
|
+
note: pending.note,
|
|
505
|
+
supersededBy,
|
|
506
|
+
};
|
|
507
|
+
entry.message.retryRecovery = retryRecovery;
|
|
508
|
+
recoveredErrors.push({
|
|
509
|
+
entryId: entry.id,
|
|
510
|
+
persistenceKey: pending.persistenceKey,
|
|
511
|
+
note: retryRecovery.note,
|
|
512
|
+
retryRecovery,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
if (recoveredErrors.length > 0) {
|
|
516
|
+
await this.#host.sessionManager.rewriteEntries();
|
|
517
|
+
}
|
|
518
|
+
return recoveredErrors;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async #handleEmptyAssistantStop(assistantMessage: AssistantMessage): Promise<boolean> {
|
|
522
|
+
if (!this.#isEmptyAssistantStop(assistantMessage)) {
|
|
523
|
+
this.#emptyStopRetryCount = 0;
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (this.#acceptTerminalEmptyStopForPrompt && assistantMessage.stopReason === "stop") {
|
|
528
|
+
this.#acceptTerminalEmptyStopForPrompt = false;
|
|
529
|
+
this.#discardAcceptedTerminalEmptyStop(assistantMessage);
|
|
530
|
+
this.#emptyStopRetryCount = 0;
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
this.#emptyStopRetryCount++;
|
|
535
|
+
if (this.#emptyStopRetryCount > EMPTY_STOP_MAX_RETRIES) {
|
|
536
|
+
const attempts = this.#emptyStopRetryCount - 1;
|
|
537
|
+
const finalError =
|
|
538
|
+
"Assistant returned empty stop after retry cap; try switching models or `/shake images` to remove archived frames";
|
|
539
|
+
logger.warn(finalError, {
|
|
540
|
+
attempts,
|
|
541
|
+
model: assistantMessage.model,
|
|
542
|
+
provider: assistantMessage.provider,
|
|
543
|
+
});
|
|
544
|
+
await this.#host.emitSessionEvent({
|
|
545
|
+
type: "auto_retry_end",
|
|
546
|
+
success: false,
|
|
547
|
+
attempt: this.#retryAttempt > 0 ? this.#retryAttempt : attempts,
|
|
548
|
+
finalError,
|
|
549
|
+
});
|
|
550
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
551
|
+
this.#retryAttempt = 0;
|
|
552
|
+
this.resolveRetry();
|
|
553
|
+
// A zero-content turn carries no transcript value, while its provider usage
|
|
554
|
+
// can anchor the next prompt at the full failed-request size and re-trigger
|
|
555
|
+
// compaction at the same boundary. Remove every capped empty stop; toolUse
|
|
556
|
+
// orphans still need this for Anthropic message-history validity.
|
|
557
|
+
await this.dropPersistedAssistantTurn(assistantMessage);
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
this.discardAssistantTurn(assistantMessage);
|
|
561
|
+
this.#host.agent.appendMessage({
|
|
562
|
+
role: "developer",
|
|
563
|
+
content: [{ type: "text", text: this.#emptyStopRetryReminder() }],
|
|
564
|
+
attribution: "agent",
|
|
565
|
+
timestamp: Date.now(),
|
|
566
|
+
});
|
|
567
|
+
this.#host.scheduleAgentContinue({ generation: this.#host.promptGeneration() });
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
#isEmptyAssistantStop(assistantMessage: AssistantMessage): boolean {
|
|
572
|
+
switch (assistantMessage.stopReason) {
|
|
573
|
+
case "stop":
|
|
574
|
+
// Unsigned thinking alone is not actionable, but a signature is
|
|
575
|
+
// provider-authenticated content and makes the stop terminal.
|
|
576
|
+
for (const content of assistantMessage.content) {
|
|
577
|
+
if (content.type === "toolCall") return false;
|
|
578
|
+
if (content.type === "text" && hasNonWhitespace(content.text)) return false;
|
|
579
|
+
if (content.type === "thinking" && hasNonWhitespace(content.thinkingSignature ?? "")) return false;
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
case "toolUse":
|
|
583
|
+
// An orphaned toolUse stop (no tool_use block) corrupts Anthropic history:
|
|
584
|
+
// a later tool_result has nothing to anchor to. Thinking alone cannot anchor
|
|
585
|
+
// a tool_result, so it does not rescue a toolUse stop here.
|
|
586
|
+
for (const content of assistantMessage.content) {
|
|
587
|
+
if (content.type === "toolCall") return false;
|
|
588
|
+
if (content.type === "text" && hasNonWhitespace(content.text)) return false;
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
default:
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
#emptyStopRetryReminder(): string {
|
|
597
|
+
return prompt.render(emptyStopRetryTemplate, {
|
|
598
|
+
retryCount: this.#emptyStopRetryCount,
|
|
599
|
+
maxRetries: EMPTY_STOP_MAX_RETRIES,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
async #handleUnexpectedAssistantStop(assistantMessage: AssistantMessage): Promise<boolean> {
|
|
603
|
+
if (!this.#host.settings.get("features.unexpectedStopDetection")) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
if (!isUnexpectedStopCandidate(assistantMessage)) {
|
|
607
|
+
this.#unexpectedStopRetryCount = 0;
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
let text = assistantMessage.content
|
|
612
|
+
.filter((content): content is TextContent => content.type === "text")
|
|
613
|
+
.map(content => content.text)
|
|
614
|
+
.join("\n");
|
|
615
|
+
// Thinking-only stops carry their signal in the thinking block (a trapped
|
|
616
|
+
// response or a truncated fragment); classify on that when there is no text.
|
|
617
|
+
if (!hasNonWhitespace(text)) {
|
|
618
|
+
text = assistantMessage.content
|
|
619
|
+
.filter((content): content is ThinkingContent => content.type === "thinking")
|
|
620
|
+
.map(content => content.thinking)
|
|
621
|
+
.join("\n");
|
|
622
|
+
}
|
|
623
|
+
if (!hasNonWhitespace(text)) {
|
|
624
|
+
this.#unexpectedStopRetryCount = 0;
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const controller = new AbortController();
|
|
629
|
+
const timeout = setTimeout(() => controller.abort(), UNEXPECTED_STOP_TIMEOUT_MS);
|
|
630
|
+
let classification: boolean | undefined;
|
|
631
|
+
try {
|
|
632
|
+
classification = await classifyUnexpectedStop(text, {
|
|
633
|
+
settings: this.#host.settings,
|
|
634
|
+
registry: this.#host.modelRegistry,
|
|
635
|
+
sessionId: this.#host.sessionId(),
|
|
636
|
+
metadataResolver: (provider: string) => this.#host.agent.metadataForProvider(provider),
|
|
637
|
+
signal: controller.signal,
|
|
638
|
+
});
|
|
639
|
+
} finally {
|
|
640
|
+
clearTimeout(timeout);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (classification !== true) {
|
|
644
|
+
this.#unexpectedStopRetryCount = 0;
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
this.#unexpectedStopRetryCount++;
|
|
649
|
+
if (this.#unexpectedStopRetryCount > UNEXPECTED_STOP_MAX_RETRIES) {
|
|
650
|
+
logger.warn("Assistant returned unexpected stop after retry cap", {
|
|
651
|
+
attempts: this.#unexpectedStopRetryCount - 1,
|
|
652
|
+
model: assistantMessage.model,
|
|
653
|
+
provider: assistantMessage.provider,
|
|
654
|
+
});
|
|
655
|
+
this.#unexpectedStopRetryCount = 0;
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
this.#host.agent.appendMessage({
|
|
660
|
+
role: "developer",
|
|
661
|
+
content: [{ type: "text", text: this.#unexpectedStopRetryReminder() }],
|
|
662
|
+
attribution: "agent",
|
|
663
|
+
timestamp: Date.now(),
|
|
664
|
+
});
|
|
665
|
+
this.#host.scheduleAgentContinue({ generation: this.#host.promptGeneration() });
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
#unexpectedStopRetryReminder(): string {
|
|
670
|
+
return prompt.render(unexpectedStopRetryTemplate, {
|
|
671
|
+
retryCount: this.#unexpectedStopRetryCount,
|
|
672
|
+
maxRetries: UNEXPECTED_STOP_MAX_RETRIES,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
removeAssistantMessageFromActiveContext(
|
|
677
|
+
assistantMessage: AssistantMessage,
|
|
678
|
+
reason = "assistant-context-cleanup",
|
|
679
|
+
): void {
|
|
680
|
+
const messages = this.#host.agent.state.messages;
|
|
681
|
+
const lastMessage = messages[messages.length - 1];
|
|
682
|
+
const lastAssistant: AssistantMessage | undefined = lastMessage?.role === "assistant" ? lastMessage : undefined;
|
|
683
|
+
if (lastAssistant !== undefined && this.#isSameAssistantMessage(lastAssistant, assistantMessage)) {
|
|
684
|
+
this.#host.agent.replaceMessages(messages.slice(0, -1));
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
// A miss means the failed turn is still in active context (or was never
|
|
688
|
+
// there); log just enough to explain why the identity check failed.
|
|
689
|
+
logger.debug("agent active context assistant removal missed", {
|
|
690
|
+
reason,
|
|
691
|
+
lastRole: lastMessage?.role,
|
|
692
|
+
candidateTimestamp: assistantMessage.timestamp,
|
|
693
|
+
lastTimestamp: lastAssistant?.timestamp,
|
|
694
|
+
candidateStopReason: assistantMessage.stopReason,
|
|
695
|
+
lastStopReason: lastAssistant?.stopReason,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Drop a recoverable assistant turn from the persisted session branch once a
|
|
701
|
+
* recovery path (context promotion or compaction) is committed. Waits for the
|
|
702
|
+
* in-flight `message_end` persistence slot first so the branch entry exists
|
|
703
|
+
* before we reparent past it. Active context removal is the caller's
|
|
704
|
+
* responsibility — recovery paths clear it eagerly so the retry never
|
|
705
|
+
* replays the failed turn, while no-recovery paths leave the persisted entry
|
|
706
|
+
* (and the user-visible transcript line) in place.
|
|
707
|
+
*/
|
|
708
|
+
async #dropPersistedAssistantTurn(assistantMessage: AssistantMessage): Promise<void> {
|
|
709
|
+
await this.#host.waitForSessionMessagePersistence(assistantMessage);
|
|
710
|
+
this.discardAssistantTurn(assistantMessage);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Drop the failed assistant turn from persisted history, run
|
|
715
|
+
* {@link #runAutoCompaction} for an `overflow` / `incomplete` recovery, and
|
|
716
|
+
* restore the assistant entry if compaction did not actually commit
|
|
717
|
+
* anything (no usable model/preparation, hook cancel, compaction error,
|
|
718
|
+
* or a no-progress automatic-continuation block before any summary was
|
|
719
|
+
* written).
|
|
720
|
+
*
|
|
721
|
+
* Compaction has to see a clean branch — otherwise its `prepareCompaction`
|
|
722
|
+
* pass would keep the failed turn in the kept region and the retry would
|
|
723
|
+
* replay it. But a return that was not paired with a fresh compaction
|
|
724
|
+
* summary or a successful history rewrite means no recovery is in progress,
|
|
725
|
+
* even if queued user input gets drained next. Restoring the failed turn
|
|
726
|
+
* before that continuation preserves the visible stop reason and rebuilds the
|
|
727
|
+
* active assistant tail that `Agent.continue()` needs to dequeue follow-ups.
|
|
728
|
+
*/
|
|
729
|
+
async #runRecoveryCompactionWithRollback(
|
|
730
|
+
reason: "overflow" | "incomplete",
|
|
731
|
+
assistantMessage: AssistantMessage,
|
|
732
|
+
allowDefer: boolean,
|
|
733
|
+
options: { autoContinue: boolean; triggerContextTokens?: number },
|
|
734
|
+
): Promise<RecoveryCompactionResult> {
|
|
735
|
+
const compactionEntryBefore = getLatestCompactionEntry(this.#host.sessionManager.getBranch());
|
|
736
|
+
await this.dropPersistedAssistantTurn(assistantMessage);
|
|
737
|
+
const result = await this.#host.runAutoCompaction(reason, true, false, allowDefer, {
|
|
738
|
+
autoContinue: options.autoContinue,
|
|
739
|
+
triggerContextTokens: options.triggerContextTokens,
|
|
740
|
+
phase: "mid_turn",
|
|
741
|
+
});
|
|
742
|
+
const compactionEntryAfter = getLatestCompactionEntry(this.#host.sessionManager.getBranch());
|
|
743
|
+
if (result.historyRewritten !== true && compactionEntryAfter === compactionEntryBefore) {
|
|
744
|
+
this.#restoreFailedAssistantTurn(assistantMessage);
|
|
745
|
+
}
|
|
746
|
+
return result;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
#restoreFailedAssistantTurn(assistantMessage: AssistantMessage): void {
|
|
750
|
+
if (!isEmptyErrorTurn(assistantMessage)) this.#host.sessionManager.appendMessage(assistantMessage);
|
|
751
|
+
const lastMessage = this.#host.agent.state.messages.at(-1);
|
|
752
|
+
if (
|
|
753
|
+
lastMessage?.role === "assistant" &&
|
|
754
|
+
this.#isSameAssistantMessage(lastMessage as AssistantMessage, assistantMessage)
|
|
755
|
+
) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
this.#host.agent.appendMessage(assistantMessage);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
#discardAcceptedTerminalEmptyStop(assistantMessage: AssistantMessage): void {
|
|
762
|
+
const branch = this.#host.sessionManager.getBranch();
|
|
763
|
+
const branchEntry = branch
|
|
764
|
+
.slice()
|
|
765
|
+
.reverse()
|
|
766
|
+
.find(
|
|
767
|
+
entry =>
|
|
768
|
+
entry.type === "message" &&
|
|
769
|
+
entry.message.role === "assistant" &&
|
|
770
|
+
this.#isSameAssistantMessage(entry.message, assistantMessage),
|
|
771
|
+
);
|
|
772
|
+
const parentEntry =
|
|
773
|
+
branchEntry?.parentId === null || branchEntry?.parentId === undefined
|
|
774
|
+
? undefined
|
|
775
|
+
: branch.find(entry => entry.id === branchEntry.parentId);
|
|
776
|
+
const prunePrompt = parentEntry?.type === "custom_message";
|
|
777
|
+
|
|
778
|
+
this.removeAssistantMessageFromActiveContext(assistantMessage, "accepted-terminal-empty-stop");
|
|
779
|
+
if (prunePrompt && this.#host.agent.state.messages.at(-1)?.role === "custom") {
|
|
780
|
+
this.#host.agent.replaceMessages(this.#host.agent.state.messages.slice(0, -1));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (!branchEntry) return;
|
|
784
|
+
const targetParentId = prunePrompt ? parentEntry.parentId : branchEntry.parentId;
|
|
785
|
+
this.#host.withBashBranchTransition(() => {
|
|
786
|
+
if (targetParentId === null) {
|
|
787
|
+
this.#host.sessionManager.resetLeaf();
|
|
788
|
+
} else {
|
|
789
|
+
this.#host.sessionManager.branch(targetParentId);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
this.#host.sessionManager.appendCustomEntry("accepted-terminal-empty-stop");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Drop an assistant turn from BOTH the live agent context and the persisted
|
|
797
|
+
* session branch (reparenting the leaf to the turn's parent), so a discarded
|
|
798
|
+
* turn does not resurface on reload. Used for empty/reasoning-only stops and
|
|
799
|
+
* the Gemini header-runaway interrupt, which must not replay a partial,
|
|
800
|
+
* loop-fueling thinking block.
|
|
801
|
+
*/
|
|
802
|
+
discardAssistantTurn(assistantMessage: AssistantMessage): void {
|
|
803
|
+
this.removeAssistantMessageFromActiveContext(assistantMessage);
|
|
804
|
+
|
|
805
|
+
const branch = this.#host.sessionManager.getBranch();
|
|
806
|
+
const persistedEntryId = this.#host.persistedAssistantEntryId(assistantMessage);
|
|
807
|
+
const branchEntry =
|
|
808
|
+
(persistedEntryId === undefined
|
|
809
|
+
? undefined
|
|
810
|
+
: branch.find(
|
|
811
|
+
entry =>
|
|
812
|
+
entry.id === persistedEntryId && entry.type === "message" && entry.message.role === "assistant",
|
|
813
|
+
)) ??
|
|
814
|
+
branch
|
|
815
|
+
.slice()
|
|
816
|
+
.reverse()
|
|
817
|
+
.find(
|
|
818
|
+
entry =>
|
|
819
|
+
entry.type === "message" &&
|
|
820
|
+
entry.message.role === "assistant" &&
|
|
821
|
+
this.#isSameAssistantMessage(entry.message as AssistantMessage, assistantMessage),
|
|
822
|
+
);
|
|
823
|
+
if (!branchEntry) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
this.#host.withBashBranchTransition(() => {
|
|
827
|
+
if (branchEntry.parentId === null) {
|
|
828
|
+
this.#host.sessionManager.resetLeaf();
|
|
829
|
+
} else {
|
|
830
|
+
this.#host.sessionManager.branch(branchEntry.parentId);
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
#isSameAssistantMessage(left: AssistantMessage, right: AssistantMessage): boolean {
|
|
836
|
+
return (
|
|
837
|
+
left === right ||
|
|
838
|
+
(left.timestamp === right.timestamp &&
|
|
839
|
+
left.provider === right.provider &&
|
|
840
|
+
left.model === right.model &&
|
|
841
|
+
left.stopReason === right.stopReason &&
|
|
842
|
+
left.errorMessage === right.errorMessage &&
|
|
843
|
+
Bun.hash(JSON.stringify(left.content)) === Bun.hash(JSON.stringify(right.content)))
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Classify retry decisions against the active session model. Test stream
|
|
849
|
+
* shims and provider adapters can emit generic assistant metadata, but retry
|
|
850
|
+
* policy belongs to the model that was actually requested for this turn.
|
|
851
|
+
*/
|
|
852
|
+
#classifyRetryMessage(message: AssistantMessage): number {
|
|
853
|
+
const activeModel = this.#host.model();
|
|
854
|
+
if (!activeModel || message.api === activeModel.api) {
|
|
855
|
+
return AIError.classifyMessage(message);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const id = AIError.classifyMessage({
|
|
859
|
+
api: activeModel.api,
|
|
860
|
+
errorId: message.errorId,
|
|
861
|
+
errorMessage: message.errorMessage,
|
|
862
|
+
errorStatus: message.errorStatus,
|
|
863
|
+
});
|
|
864
|
+
message.errorId = id;
|
|
865
|
+
return id;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
#isUsagePreflightBlocked(message: AssistantMessage): boolean {
|
|
869
|
+
return message.errorMessage?.startsWith(USAGE_PREFLIGHT_BLOCKED_PREFIX) === true;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Retry an empty, reason-less provider abort: a turn with no content that
|
|
873
|
+
* carries the generic sentinel (bare `abort()`), whether the provider
|
|
874
|
+
* finalized it as `stopReason: "aborted"` or leaked it as `stopReason:
|
|
875
|
+
* "error"` (a stalled/dropped stream reported as an error rather than an
|
|
876
|
+
* abort — issue #5375). Only fires while the session is neither aborting nor
|
|
877
|
+
* tearing down. A user/lifecycle abort (`#abortInProgress`), a dispose-driven
|
|
878
|
+
* abort (`#isDisposed`), or a session-induced streaming-edit guard abort
|
|
879
|
+
* (`StreamingEditGuard.abortTriggered` — auto-generated-file guard or failed-patch
|
|
880
|
+
* preview) is deliberate and MUST settle the turn instead: routing it through
|
|
881
|
+
* retry would orphan `#retryPromise` on a continuation the guard skips
|
|
882
|
+
* (hanging the in-flight `prompt()`) or silently undo the guard's intended
|
|
883
|
+
* abort. Deliberate user interrupts (`UserInterrupt`) and silent aborts carry
|
|
884
|
+
* their own marker, not the generic sentinel, so they never match here.
|
|
885
|
+
*/
|
|
886
|
+
isRetryableReasonlessAbort(message: AssistantMessage): boolean {
|
|
887
|
+
if (
|
|
888
|
+
(message.stopReason !== "aborted" && message.stopReason !== "error") ||
|
|
889
|
+
message.content.length !== 0 ||
|
|
890
|
+
this.#host.abortInProgress() ||
|
|
891
|
+
this.#host.isDisposed() ||
|
|
892
|
+
this.#host.streamingEditAbortTriggered()
|
|
893
|
+
) {
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const id = this.#classifyRetryMessage(message);
|
|
898
|
+
if (message.stopReason === "aborted" && AIError.is(id, AIError.Flag.Abort)) return true;
|
|
899
|
+
if (message.errorMessage !== "Request was aborted" && message.errorMessage !== "Request was aborted.") {
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
message.errorId = AIError.create(AIError.Flag.Abort);
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Check if an error is retryable (transient errors or usage limits).
|
|
909
|
+
* Context overflow is NOT retryable (handled by compaction instead).
|
|
910
|
+
* Usage-limit errors are retryable because the retry handler performs credential switching.
|
|
911
|
+
*/
|
|
912
|
+
isRetryableError(message: AssistantMessage): boolean {
|
|
913
|
+
if (message.stopReason !== "error") return false;
|
|
914
|
+
if (this.#isUsagePreflightBlocked(message)) return false;
|
|
915
|
+
|
|
916
|
+
const id = this.#classifyRetryMessage(message);
|
|
917
|
+
// Context overflow is handled by compaction, not retry
|
|
918
|
+
const contextWindow = this.#host.model()?.contextWindow ?? 0;
|
|
919
|
+
if (AIError.isContextOverflow(message, contextWindow)) return false;
|
|
920
|
+
|
|
921
|
+
// A classifier refusal/sensitivity stop is the model's decision, not a route
|
|
922
|
+
// failure, but only after we confirm no replay-unsafe output has already
|
|
923
|
+
// streamed. Committed text, images, tool calls, or server tools must not be
|
|
924
|
+
// discarded and replayed.
|
|
925
|
+
if (this.#hasReplayUnsafeOutput(message)) return false;
|
|
926
|
+
if (this.isClassifierRefusal(message)) return true;
|
|
927
|
+
return AIError.retriable(id);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Classify a reasonless abort or stream stall whose emitted tool calls all
|
|
932
|
+
* have results. The failed assistant/tool-result pair stays in context so
|
|
933
|
+
* continuation cannot replay completed side effects; synthetic results tell
|
|
934
|
+
* the next turn that an unexecuted call must be reissued.
|
|
935
|
+
*/
|
|
936
|
+
classifyResolvedInterruptedToolTurn(message: AssistantMessage): "reasonless-abort" | "stream-stall" | undefined {
|
|
937
|
+
const id = this.#classifyRetryMessage(message);
|
|
938
|
+
const genericAbort =
|
|
939
|
+
message.errorMessage === "Request was aborted" || message.errorMessage === "Request was aborted.";
|
|
940
|
+
const reasonlessAbort =
|
|
941
|
+
(message.stopReason === "aborted" || message.stopReason === "error") &&
|
|
942
|
+
!this.#host.abortInProgress() &&
|
|
943
|
+
!this.#host.isDisposed() &&
|
|
944
|
+
!this.#host.streamingEditAbortTriggered() &&
|
|
945
|
+
((message.stopReason === "aborted" && AIError.is(id, AIError.Flag.Abort)) || genericAbort);
|
|
946
|
+
const streamStall =
|
|
947
|
+
message.stopReason === "error" &&
|
|
948
|
+
message.errorMessage?.toLowerCase().includes("stream stall") === true &&
|
|
949
|
+
AIError.retriable(id);
|
|
950
|
+
if (!reasonlessAbort && !streamStall) return undefined;
|
|
951
|
+
if (reasonlessAbort && genericAbort) message.errorId = AIError.create(AIError.Flag.Abort);
|
|
952
|
+
|
|
953
|
+
// The Cursor server-execution marker gate applies only to the stream-stall
|
|
954
|
+
// path: an unmarked/unresolved Cursor block there means the server has not
|
|
955
|
+
// finished executing, so resuming would race it. A reasonless abort instead
|
|
956
|
+
// ends the turn and the agent loop pairs every un-run call (Cursor's unmarked
|
|
957
|
+
// `todo`/MCP blocks included) with a synthetic `executed: false` result, so
|
|
958
|
+
// the tool-result reconciliation below is the safety gate and the marker is
|
|
959
|
+
// irrelevant.
|
|
960
|
+
const resolvedToolCallIds: string[] = [];
|
|
961
|
+
for (const block of message.content) {
|
|
962
|
+
if (block.type !== "toolCall") continue;
|
|
963
|
+
if (
|
|
964
|
+
streamStall &&
|
|
965
|
+
message.provider === "cursor" &&
|
|
966
|
+
(!(kCursorExecResolved in block) || block[kCursorExecResolved] !== true)
|
|
967
|
+
) {
|
|
968
|
+
return undefined;
|
|
969
|
+
}
|
|
970
|
+
resolvedToolCallIds.push(block.id);
|
|
971
|
+
}
|
|
972
|
+
if (resolvedToolCallIds.length === 0) return undefined;
|
|
973
|
+
|
|
974
|
+
const messages = this.#host.agent.state.messages;
|
|
975
|
+
let assistantIndex = -1;
|
|
976
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
977
|
+
const candidate = messages[i];
|
|
978
|
+
if (candidate.role === "assistant" && this.#isSameAssistantMessage(candidate, message)) {
|
|
979
|
+
assistantIndex = i;
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (assistantIndex < 0) return undefined;
|
|
984
|
+
|
|
985
|
+
const unresolvedToolCallIds = new Set(resolvedToolCallIds);
|
|
986
|
+
for (let i = assistantIndex + 1; i < messages.length; i++) {
|
|
987
|
+
const candidate = messages[i];
|
|
988
|
+
if (candidate.role === "toolResult") unresolvedToolCallIds.delete(candidate.toolCallId);
|
|
989
|
+
}
|
|
990
|
+
if (unresolvedToolCallIds.size > 0) return undefined;
|
|
991
|
+
return reasonlessAbort ? "reasonless-abort" : "stream-stall";
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Retried turns remove the failed assistant message from active context.
|
|
995
|
+
* Thinking-only partials are safe to discard and replay: reasoning models
|
|
996
|
+
* routinely stall after long thinking with no visible output, and duplicated
|
|
997
|
+
* thinking display is materially lower harm than duplicated final text.
|
|
998
|
+
* Whitespace-only and buffered text are likewise safe since nothing meaningful
|
|
999
|
+
* reached the user. Committed text, generated images, server tools, and retained
|
|
1000
|
+
* tool calls are NOT safe: each has already rendered or may have side effects,
|
|
1001
|
+
* so replaying the turn can duplicate user-visible output or work.
|
|
1002
|
+
*/
|
|
1003
|
+
#hasReplayUnsafeOutput(message: AssistantMessage): boolean {
|
|
1004
|
+
return message.content.some(
|
|
1005
|
+
block =>
|
|
1006
|
+
block.type === "toolCall" ||
|
|
1007
|
+
block.type === "image" ||
|
|
1008
|
+
block.type === "anthropicServerTool" ||
|
|
1009
|
+
(block.type === "text" && this.#host.textOutputCommitted() && block.text.trim().length > 0),
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* OpenRouter can repeatedly close Gemini streams at the reasoning-to-payload
|
|
1015
|
+
* transition. One retry covers a transient edge failure; the normal ten-retry
|
|
1016
|
+
* budget would otherwise re-run the same expensive reasoning cycle unchanged.
|
|
1017
|
+
*/
|
|
1018
|
+
#isOpenRouterThinkingStreamClose(message: AssistantMessage): boolean {
|
|
1019
|
+
return (
|
|
1020
|
+
message.provider === "openrouter" &&
|
|
1021
|
+
/server_error:\s*stream closed with reason:\s*error/i.test(message.errorMessage ?? "") &&
|
|
1022
|
+
message.content.some(block => block.type === "thinking" && block.thinking.trim().length > 0)
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/** Checks whether a provider error represents a classifier refusal. */
|
|
1027
|
+
isClassifierRefusal(message: AssistantMessage): boolean {
|
|
1028
|
+
if (message.stopReason !== "error") return false;
|
|
1029
|
+
const stopType = message.stopDetails?.type;
|
|
1030
|
+
return stopType === "refusal" || stopType === "sensitive";
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
#getRetryFallbackResolutionContext(): RetryFallbackResolutionContext {
|
|
1034
|
+
return {
|
|
1035
|
+
chains: this.#getRetryFallbackChains(),
|
|
1036
|
+
getModelRole: role => this.#host.settings.getModelRole(role),
|
|
1037
|
+
modelLookup: this.#host.modelRegistry,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
#getRetryFallbackChains(): RetryFallbackChains {
|
|
1041
|
+
return getRetryFallbackChains(this.#host.settings);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
#validateRetryFallbackChains(): void {
|
|
1045
|
+
validateRetryFallbackChains(this.#host.settings, this.#host.modelRegistry, message =>
|
|
1046
|
+
this.#host.configWarnings.push(message),
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
#getRetryFallbackRevertPolicy(): RetryFallbackRevertPolicy {
|
|
1051
|
+
return getRetryFallbackRevertPolicy(this.#host.settings);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** Clears fallback ownership after an explicit model change. */
|
|
1055
|
+
clearActiveRetryFallback(): void {
|
|
1056
|
+
this.#activeRetryFallback = undefined;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/** Checks whether a fallback selector remains in cooldown. */
|
|
1060
|
+
isRetryFallbackSelectorSuppressed(selector: RetryFallbackSelector): boolean {
|
|
1061
|
+
return this.#host.modelRegistry.isSelectorSuppressed(selector.raw);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/** Records the cooldown that should suppress a failing selector. */
|
|
1065
|
+
noteRetryFallbackCooldown(currentSelector: string, retryAfterMs: number | undefined, errorMessage: string): void {
|
|
1066
|
+
let cooldownMs = retryAfterMs;
|
|
1067
|
+
if (!cooldownMs || cooldownMs <= 0) {
|
|
1068
|
+
const reason = parseRateLimitReason(errorMessage);
|
|
1069
|
+
cooldownMs = reason === "UNKNOWN" ? 5 * 60 * 1000 : calculateRateLimitBackoffMs(reason);
|
|
1070
|
+
}
|
|
1071
|
+
this.#host.modelRegistry.suppressSelector(currentSelector, Date.now() + cooldownMs);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Map the failing model selector to the chain key that owns it, by
|
|
1076
|
+
* specificity: an exact model-selector key, then a `provider/*` wildcard,
|
|
1077
|
+
* then a model role whose current assignment matches, then `default`.
|
|
1078
|
+
* Model-oriented keys win over roles so a chain follows the model across
|
|
1079
|
+
* role reassignments.
|
|
1080
|
+
*/
|
|
1081
|
+
resolveRetryFallbackRole(
|
|
1082
|
+
currentSelector: string,
|
|
1083
|
+
currentModel: Model | null | undefined = this.#host.model(),
|
|
1084
|
+
): string | undefined {
|
|
1085
|
+
return resolveRetryFallbackChainKey(this.#getRetryFallbackResolutionContext(), currentSelector, currentModel);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/** Finds fallback candidates that follow the active selector. */
|
|
1089
|
+
findRetryFallbackCandidates(
|
|
1090
|
+
role: string,
|
|
1091
|
+
currentSelector: string,
|
|
1092
|
+
currentModel: Model | null | undefined = this.#host.model(),
|
|
1093
|
+
): RetryFallbackSelector[] {
|
|
1094
|
+
return findRetryFallbackCandidates(
|
|
1095
|
+
this.#getRetryFallbackResolutionContext(),
|
|
1096
|
+
role,
|
|
1097
|
+
currentSelector,
|
|
1098
|
+
currentModel,
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
async #maybeApplyUsageAwareFallback(signal: AbortSignal, confirmer?: UsageFallbackConfirmer): Promise<boolean> {
|
|
1103
|
+
if (!this.#host.settings.get("retry.usageAwareFallback")) return false;
|
|
1104
|
+
const currentModel = this.#host.model();
|
|
1105
|
+
if (!currentModel) return false;
|
|
1106
|
+
const currentSelector = formatRetryFallbackSelector(currentModel, this.#host.thinkingLevel());
|
|
1107
|
+
let health: ModelUsageHealth;
|
|
1108
|
+
try {
|
|
1109
|
+
health = await this.#host.modelRegistry.authStorage.getModelUsageHealth(currentModel.provider, {
|
|
1110
|
+
modelId: currentModel.id,
|
|
1111
|
+
sessionId: this.#host.sessionId(),
|
|
1112
|
+
baseUrl: currentModel.baseUrl,
|
|
1113
|
+
reserveFraction: this.#host.settings.get("retry.usageReservePct") / 100,
|
|
1114
|
+
signal,
|
|
1115
|
+
});
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1118
|
+
logger.debug("Usage-aware runtime preflight failed open", {
|
|
1119
|
+
provider: currentModel.provider,
|
|
1120
|
+
model: currentModel.id,
|
|
1121
|
+
error: String(error),
|
|
1122
|
+
});
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1126
|
+
const selectedAccount = health.accounts.find(account => account.selected);
|
|
1127
|
+
if (health.state === "healthy") {
|
|
1128
|
+
this.#usageReserveApprovedSelector = undefined;
|
|
1129
|
+
if (
|
|
1130
|
+
selectedAccount &&
|
|
1131
|
+
selectedAccount.state !== "healthy" &&
|
|
1132
|
+
health.accounts.some(account => account.state === "healthy")
|
|
1133
|
+
) {
|
|
1134
|
+
this.#host.modelRegistry.authStorage.releaseSessionCredentialForReselection(
|
|
1135
|
+
currentModel.provider,
|
|
1136
|
+
this.#host.sessionId(),
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
if (health.state === "unknown") {
|
|
1142
|
+
this.#usageReserveApprovedSelector = undefined;
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
if (health.state !== "reserve") this.#usageReserveApprovedSelector = undefined;
|
|
1146
|
+
|
|
1147
|
+
const reservePolicy = this.#host.settings.get("retry.usageReservePolicy");
|
|
1148
|
+
if (reservePolicy === "fail-closed") {
|
|
1149
|
+
const condition = health.state === "reserve" ? "reserve reached" : "usage depleted";
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
`${USAGE_PREFLIGHT_BLOCKED_PREFIX} ${condition} for ${currentSelector}; reserve policy is fail-closed.`,
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
if (
|
|
1155
|
+
reservePolicy === "confirm" &&
|
|
1156
|
+
health.state === "reserve" &&
|
|
1157
|
+
this.#usageReserveApprovedSelector === currentSelector
|
|
1158
|
+
) {
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1161
|
+
if (!this.#host.settings.get("retry.modelFallback")) return false;
|
|
1162
|
+
|
|
1163
|
+
const role = this.#activeRetryFallback?.role ?? this.resolveRetryFallbackRole(currentSelector, currentModel);
|
|
1164
|
+
if (!role) return false;
|
|
1165
|
+
let fallback: { selector: RetryFallbackSelector; apiKey: string } | undefined;
|
|
1166
|
+
const ceiling = this.#host.thinkingLevelCeiling();
|
|
1167
|
+
for (const candidate of this.findRetryFallbackCandidates(role, currentSelector, currentModel)) {
|
|
1168
|
+
if (this.isRetryFallbackSelectorSuppressed(candidate)) continue;
|
|
1169
|
+
const resolved = resolveModelOverride([candidate.raw], this.#host.modelRegistry, this.#host.settings);
|
|
1170
|
+
const candidateModel = resolved.model ?? this.#host.modelRegistry.find(candidate.provider, candidate.id);
|
|
1171
|
+
if (!candidateModel || !this.#host.modelRegistry.hasConfiguredAuth(candidateModel)) continue;
|
|
1172
|
+
if (ceiling !== undefined && !modelSupportsEffortCeiling(candidateModel, ceiling)) continue;
|
|
1173
|
+
try {
|
|
1174
|
+
const candidateHealth = await this.#host.modelRegistry.authStorage.getModelUsageHealth(
|
|
1175
|
+
candidateModel.provider,
|
|
1176
|
+
{
|
|
1177
|
+
modelId: candidateModel.id,
|
|
1178
|
+
sessionId: this.#host.sessionId(),
|
|
1179
|
+
baseUrl: candidateModel.baseUrl,
|
|
1180
|
+
reserveFraction: this.#host.settings.get("retry.usageReservePct") / 100,
|
|
1181
|
+
signal,
|
|
1182
|
+
},
|
|
1183
|
+
);
|
|
1184
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1185
|
+
if (candidateHealth.state === "depleted" || candidateHealth.state === "reserve") continue;
|
|
1186
|
+
if (candidateHealth.state === "healthy") {
|
|
1187
|
+
const selected = candidateHealth.accounts.find(account => account.selected);
|
|
1188
|
+
if (
|
|
1189
|
+
selected &&
|
|
1190
|
+
selected.state !== "healthy" &&
|
|
1191
|
+
candidateHealth.accounts.some(account => account.state === "healthy")
|
|
1192
|
+
) {
|
|
1193
|
+
this.#host.modelRegistry.authStorage.releaseSessionCredentialForReselection(
|
|
1194
|
+
candidateModel.provider,
|
|
1195
|
+
this.#host.sessionId(),
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
} catch {
|
|
1200
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1201
|
+
// Unknown usage fails open for an otherwise valid fallback.
|
|
1202
|
+
}
|
|
1203
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1204
|
+
let apiKey: string | undefined;
|
|
1205
|
+
try {
|
|
1206
|
+
apiKey = await this.#host.modelRegistry.getApiKey(candidateModel, this.#host.sessionId(), { signal });
|
|
1207
|
+
} catch {
|
|
1208
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1212
|
+
if (!apiKey) continue;
|
|
1213
|
+
fallback = { selector: candidate, apiKey };
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
if (!fallback) return false;
|
|
1217
|
+
|
|
1218
|
+
let shouldFallback = health.state === "depleted" || reservePolicy === "auto" || !confirmer;
|
|
1219
|
+
if (!shouldFallback && health.state === "reserve" && confirmer) {
|
|
1220
|
+
const remainingFraction =
|
|
1221
|
+
selectedAccount?.remainingFraction ??
|
|
1222
|
+
health.accounts.reduce<number | undefined>((minimum, account) => {
|
|
1223
|
+
if (account.remainingFraction === undefined) return minimum;
|
|
1224
|
+
return minimum === undefined ? account.remainingFraction : Math.min(minimum, account.remainingFraction);
|
|
1225
|
+
}, undefined);
|
|
1226
|
+
shouldFallback = await this.#confirmUsageFallback(
|
|
1227
|
+
confirmer,
|
|
1228
|
+
{
|
|
1229
|
+
from: currentSelector,
|
|
1230
|
+
to: fallback.selector.raw,
|
|
1231
|
+
remainingPercent: remainingFraction === undefined ? undefined : Math.max(0, remainingFraction * 100),
|
|
1232
|
+
},
|
|
1233
|
+
signal,
|
|
1234
|
+
);
|
|
1235
|
+
if (signal.aborted || !modelsAreEqual(this.#host.model(), currentModel)) return false;
|
|
1236
|
+
}
|
|
1237
|
+
if (!shouldFallback) {
|
|
1238
|
+
this.#usageReserveApprovedSelector = currentSelector;
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
this.#usageReserveApprovedSelector = undefined;
|
|
1242
|
+
return this.applyRetryFallbackCandidate(role, fallback.selector, currentSelector, {
|
|
1243
|
+
pinFallback: true,
|
|
1244
|
+
apiKey: fallback.apiKey,
|
|
1245
|
+
signal,
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
async #confirmUsageFallback(
|
|
1250
|
+
confirmer: UsageFallbackConfirmer,
|
|
1251
|
+
confirmation: UsageFallbackConfirmation,
|
|
1252
|
+
signal: AbortSignal,
|
|
1253
|
+
): Promise<boolean> {
|
|
1254
|
+
if (signal.aborted) return false;
|
|
1255
|
+
const aborted = Promise.withResolvers<boolean>();
|
|
1256
|
+
const onAbort = () => aborted.resolve(false);
|
|
1257
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1258
|
+
try {
|
|
1259
|
+
return await Promise.race([confirmer(confirmation, signal), aborted.promise]);
|
|
1260
|
+
} finally {
|
|
1261
|
+
signal.removeEventListener("abort", onAbort);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
async applyRetryFallbackCandidate(
|
|
1266
|
+
role: string,
|
|
1267
|
+
selector: RetryFallbackSelector,
|
|
1268
|
+
currentSelector: string,
|
|
1269
|
+
options?: { pinFallback?: boolean; apiKey?: string; signal?: AbortSignal },
|
|
1270
|
+
): Promise<boolean> {
|
|
1271
|
+
const resolved = resolveModelOverride([selector.raw], this.#host.modelRegistry, this.#host.settings);
|
|
1272
|
+
const candidate = resolved.model ?? this.#host.modelRegistry.find(selector.provider, selector.id);
|
|
1273
|
+
if (!candidate) {
|
|
1274
|
+
throw new Error(`Retry fallback model not found: ${selector.raw}`);
|
|
1275
|
+
}
|
|
1276
|
+
const apiKey =
|
|
1277
|
+
options?.apiKey ??
|
|
1278
|
+
(await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId(), { signal: options?.signal }));
|
|
1279
|
+
if (!apiKey) {
|
|
1280
|
+
throw new Error(`No API key for retry fallback ${selector.raw}`);
|
|
1281
|
+
}
|
|
1282
|
+
if (options?.signal?.aborted) return false;
|
|
1283
|
+
|
|
1284
|
+
// Capture the configured selector (auto-aware) so a fallback chain preserves
|
|
1285
|
+
// `auto` instead of collapsing it to the level it resolved to this turn.
|
|
1286
|
+
const currentThinkingLevel = this.#host.configuredThinkingLevel();
|
|
1287
|
+
const requestedThinkingLevel = selector.thinkingLevel ?? currentThinkingLevel;
|
|
1288
|
+
// A fallback selector's explicit level (or the carried level after the
|
|
1289
|
+
// replacement model's floor clamp) must never exceed the session's
|
|
1290
|
+
// per-spawn effort ceiling.
|
|
1291
|
+
const nextThinkingLevel =
|
|
1292
|
+
requestedThinkingLevel === AUTO_THINKING
|
|
1293
|
+
? requestedThinkingLevel
|
|
1294
|
+
: clampThinkingLevelToCeiling(candidate, requestedThinkingLevel, this.#host.thinkingLevelCeiling());
|
|
1295
|
+
const candidateSelector = formatModelStringWithRouting(candidate);
|
|
1296
|
+
const previousModel = this.#host.model();
|
|
1297
|
+
await this.#host.setModelWithProviderSessionReset(candidate);
|
|
1298
|
+
if (options?.signal?.aborted) {
|
|
1299
|
+
if (previousModel && this.#host.model() === candidate) {
|
|
1300
|
+
await this.#host.setModelWithProviderSessionReset(previousModel);
|
|
1301
|
+
}
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
if (this.#host.model() !== candidate) return false;
|
|
1305
|
+
this.#host.sessionManager.appendModelChange(candidateSelector, EPHEMERAL_MODEL_CHANGE_ROLE, true);
|
|
1306
|
+
this.#host.settings.getStorage()?.recordModelUsage(candidateSelector);
|
|
1307
|
+
this.#host.setThinkingLevel(nextThinkingLevel);
|
|
1308
|
+
if (!this.#activeRetryFallback) {
|
|
1309
|
+
this.#activeRetryFallback = {
|
|
1310
|
+
role,
|
|
1311
|
+
originalSelector: currentSelector,
|
|
1312
|
+
originalThinkingLevel: currentThinkingLevel,
|
|
1313
|
+
lastAppliedFallbackThinkingLevel: nextThinkingLevel,
|
|
1314
|
+
pinned: options?.pinFallback === true,
|
|
1315
|
+
};
|
|
1316
|
+
} else {
|
|
1317
|
+
this.#activeRetryFallback.lastAppliedFallbackThinkingLevel = nextThinkingLevel;
|
|
1318
|
+
this.#activeRetryFallback.pinned = this.#activeRetryFallback.pinned || options?.pinFallback === true;
|
|
1319
|
+
}
|
|
1320
|
+
await this.#host.emitSessionEvent({
|
|
1321
|
+
type: "retry_fallback_applied",
|
|
1322
|
+
from: currentSelector,
|
|
1323
|
+
to: selector.raw,
|
|
1324
|
+
role,
|
|
1325
|
+
});
|
|
1326
|
+
return true;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
async #tryRetryModelFallback(currentSelector: string, options?: { pinFallback?: boolean }): Promise<boolean> {
|
|
1330
|
+
const role = this.#activeRetryFallback?.role ?? this.resolveRetryFallbackRole(currentSelector);
|
|
1331
|
+
if (!role) return false;
|
|
1332
|
+
|
|
1333
|
+
const ceiling = this.#host.thinkingLevelCeiling();
|
|
1334
|
+
for (const selector of this.findRetryFallbackCandidates(role, currentSelector)) {
|
|
1335
|
+
if (this.isRetryFallbackSelectorSuppressed(selector)) continue;
|
|
1336
|
+
const resolved = resolveModelOverride([selector.raw], this.#host.modelRegistry, this.#host.settings);
|
|
1337
|
+
const candidate = resolved.model ?? this.#host.modelRegistry.find(selector.provider, selector.id);
|
|
1338
|
+
if (!candidate) continue;
|
|
1339
|
+
// A candidate whose effort floor exceeds the per-spawn ceiling would be
|
|
1340
|
+
// clamped UP past the cap by its model floor — skip it entirely.
|
|
1341
|
+
if (ceiling !== undefined && !modelSupportsEffortCeiling(candidate, ceiling)) continue;
|
|
1342
|
+
const apiKey = await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId());
|
|
1343
|
+
if (!apiKey) continue;
|
|
1344
|
+
return this.applyRetryFallbackCandidate(role, selector, currentSelector, options);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/** The active model when it is a Fireworks Fast (`-fast`) variant, else undefined. */
|
|
1351
|
+
#activeFireworksFastModel(): Model | undefined {
|
|
1352
|
+
const model = this.#host.model();
|
|
1353
|
+
return model?.provider === "fireworks" && isFireworksFastModelId(model.id) ? model : undefined;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* True when the current turn failed on a Fireworks Fast (`-fast`) model in a
|
|
1358
|
+
* way that should degrade to the reliable base (Standard) model. Fast is a
|
|
1359
|
+
* speed-optimized router with no SLA, so any *pre-content* failure — a
|
|
1360
|
+
* transient overload/5xx or a hard "router/model not found / unsupported" —
|
|
1361
|
+
* is worth retrying on the base id. Skips failures the base model shares:
|
|
1362
|
+
* context overflow (compaction's job), usage limits and auth errors (same
|
|
1363
|
+
* account/key), and turns that already emitted any replay-unsafe output.
|
|
1364
|
+
* Requires the base model to exist in the registry.
|
|
1365
|
+
*/
|
|
1366
|
+
isFireworksFastFallbackEligible(message: AssistantMessage): boolean {
|
|
1367
|
+
const model = this.#activeFireworksFastModel();
|
|
1368
|
+
if (!model) return false;
|
|
1369
|
+
if (message.stopReason !== "error") return false;
|
|
1370
|
+
if (this.#isUsagePreflightBlocked(message)) return false;
|
|
1371
|
+
if (this.#hasReplayUnsafeOutput(message)) return false;
|
|
1372
|
+
// A content refusal/sensitivity stop is the model's decision, not a route
|
|
1373
|
+
// failure — switching to the base model would just re-trigger it.
|
|
1374
|
+
if (this.isClassifierRefusal(message)) return false;
|
|
1375
|
+
const id = this.#classifyRetryMessage(message);
|
|
1376
|
+
if (AIError.isContextOverflow(message, model.contextWindow ?? 0)) return false;
|
|
1377
|
+
if (AIError.is(id, AIError.Flag.UsageLimit)) return false;
|
|
1378
|
+
if (AIError.is(id, AIError.Flag.AuthFailed)) return false;
|
|
1379
|
+
return this.#host.modelRegistry.find("fireworks", toFireworksBaseModelId(model.id)) !== undefined;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* True when a turn failed with a hard (non-retryable) provider error but a
|
|
1384
|
+
* configured `retry.fallbackChains` entry covers the active model: the same
|
|
1385
|
+
* model is not worth retrying, yet a DIFFERENT model is a fresh chance, so
|
|
1386
|
+
* the chain is consulted before the error becomes final. Skips failures a
|
|
1387
|
+
* model switch cannot fix or must not replay: cancellations (abort-flavored
|
|
1388
|
+
* errors are not model faults), context overflow (compaction's job),
|
|
1389
|
+
* classifier refusals (chain consult is handled on the retryable path with
|
|
1390
|
+
* `pinFallback`), and turns that already emitted replay-unsafe output.
|
|
1391
|
+
*/
|
|
1392
|
+
isHardErrorFallbackEligible(message: AssistantMessage): boolean {
|
|
1393
|
+
if (message.stopReason !== "error") return false;
|
|
1394
|
+
if (this.#isUsagePreflightBlocked(message)) return false;
|
|
1395
|
+
const model = this.#host.model();
|
|
1396
|
+
if (!model) return false;
|
|
1397
|
+
const retrySettings = this.#host.settings.getGroup("retry");
|
|
1398
|
+
if (!retrySettings.enabled || !retrySettings.modelFallback) return false;
|
|
1399
|
+
if (this.isClassifierRefusal(message)) return false;
|
|
1400
|
+
const id = this.#classifyRetryMessage(message);
|
|
1401
|
+
if (AIError.is(id, AIError.Flag.Abort) || AIError.is(id, AIError.Flag.UserInterrupt)) return false;
|
|
1402
|
+
if (AIError.isContextOverflow(message, model.contextWindow ?? 0)) return false;
|
|
1403
|
+
if (this.#hasReplayUnsafeOutput(message)) return false;
|
|
1404
|
+
const currentSelector = formatRetryFallbackSelector(model, this.#host.thinkingLevel());
|
|
1405
|
+
const role = this.#activeRetryFallback?.role ?? this.resolveRetryFallbackRole(currentSelector);
|
|
1406
|
+
if (!role) return false;
|
|
1407
|
+
return this.findRetryFallbackCandidates(role, currentSelector).length > 0;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Switch the active model from a Fireworks Fast (`-fast`) variant to its base
|
|
1412
|
+
* (Standard) id and stick there for the rest of the session — the auto
|
|
1413
|
+
* fallback that makes Fast a safe default. Returns false when the current
|
|
1414
|
+
* model is not a fast variant, the base id is missing, or it has no key.
|
|
1415
|
+
*/
|
|
1416
|
+
async #tryFireworksFastFallback(currentSelector: string): Promise<boolean> {
|
|
1417
|
+
const model = this.#activeFireworksFastModel();
|
|
1418
|
+
if (!model) return false;
|
|
1419
|
+
const baseModel = this.#host.modelRegistry.find("fireworks", toFireworksBaseModelId(model.id));
|
|
1420
|
+
if (!baseModel) return false;
|
|
1421
|
+
const apiKey = await this.#host.modelRegistry.getApiKey(baseModel, this.#host.sessionId());
|
|
1422
|
+
if (!apiKey) return false;
|
|
1423
|
+
const baseSelector = formatModelStringWithRouting(baseModel);
|
|
1424
|
+
await this.#host.setModelWithProviderSessionReset(baseModel);
|
|
1425
|
+
this.#host.sessionManager.appendModelChange(baseSelector, EPHEMERAL_MODEL_CHANGE_ROLE, true);
|
|
1426
|
+
this.#host.settings.getStorage()?.recordModelUsage(baseSelector);
|
|
1427
|
+
await this.#host.emitSessionEvent({
|
|
1428
|
+
type: "retry_fallback_applied",
|
|
1429
|
+
from: currentSelector,
|
|
1430
|
+
to: baseSelector,
|
|
1431
|
+
role: "fireworks-fast",
|
|
1432
|
+
});
|
|
1433
|
+
return true;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
async #maybeRestoreRetryFallbackPrimary(): Promise<void> {
|
|
1437
|
+
if (!this.#activeRetryFallback) return;
|
|
1438
|
+
if (this.#activeRetryFallback.pinned) return;
|
|
1439
|
+
if (this.#getRetryFallbackRevertPolicy() !== "cooldown-expiry") return;
|
|
1440
|
+
|
|
1441
|
+
const {
|
|
1442
|
+
originalSelector: originalSelectorRaw,
|
|
1443
|
+
originalThinkingLevel,
|
|
1444
|
+
lastAppliedFallbackThinkingLevel,
|
|
1445
|
+
} = this.#activeRetryFallback;
|
|
1446
|
+
const originalSelector = parseRetryFallbackSelector(originalSelectorRaw, this.#host.modelRegistry);
|
|
1447
|
+
if (!originalSelector) {
|
|
1448
|
+
this.clearActiveRetryFallback();
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const currentModel = this.#host.model();
|
|
1453
|
+
if (!currentModel) return;
|
|
1454
|
+
const currentSelector = formatRetryFallbackSelector(currentModel, this.#host.thinkingLevel());
|
|
1455
|
+
if (currentSelector === originalSelector.raw) {
|
|
1456
|
+
if (!this.isRetryFallbackSelectorSuppressed(originalSelector)) {
|
|
1457
|
+
this.clearActiveRetryFallback();
|
|
1458
|
+
}
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
if (this.isRetryFallbackSelectorSuppressed(originalSelector)) return;
|
|
1462
|
+
|
|
1463
|
+
const resolvedPrimary = resolveModelOverride(
|
|
1464
|
+
[originalSelector.raw],
|
|
1465
|
+
this.#host.modelRegistry,
|
|
1466
|
+
this.#host.settings,
|
|
1467
|
+
);
|
|
1468
|
+
const primaryModel =
|
|
1469
|
+
resolvedPrimary.model ?? this.#host.modelRegistry.find(originalSelector.provider, originalSelector.id);
|
|
1470
|
+
if (!primaryModel) return;
|
|
1471
|
+
const apiKey = await this.#host.modelRegistry.getApiKey(primaryModel, this.#host.sessionId());
|
|
1472
|
+
if (!apiKey) return;
|
|
1473
|
+
|
|
1474
|
+
const currentThinkingLevel = this.#host.configuredThinkingLevel();
|
|
1475
|
+
const thinkingToApply =
|
|
1476
|
+
currentThinkingLevel === lastAppliedFallbackThinkingLevel ? originalThinkingLevel : currentThinkingLevel;
|
|
1477
|
+
const primarySelector = formatModelStringWithRouting(primaryModel);
|
|
1478
|
+
await this.#host.setModelWithProviderSessionReset(primaryModel);
|
|
1479
|
+
this.#host.sessionManager.appendModelChange(primarySelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
1480
|
+
this.#host.settings.getStorage()?.recordModelUsage(primarySelector);
|
|
1481
|
+
this.#host.setThinkingLevel(thinkingToApply);
|
|
1482
|
+
this.clearActiveRetryFallback();
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
#parseRetryAfterMsFromError(errorMessage: string): number | undefined {
|
|
1486
|
+
const now = Date.now();
|
|
1487
|
+
const retryAfterMsMatch = /retry-after-ms\s*[:=]\s*(\d+)/i.exec(errorMessage);
|
|
1488
|
+
if (retryAfterMsMatch) {
|
|
1489
|
+
return Math.max(0, Number(retryAfterMsMatch[1]));
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
const retryAfterMatch = /retry-after\s*[:=]\s*([^\s,;]+)/i.exec(errorMessage);
|
|
1493
|
+
if (retryAfterMatch) {
|
|
1494
|
+
const value = retryAfterMatch[1];
|
|
1495
|
+
const seconds = Number(value);
|
|
1496
|
+
if (!Number.isNaN(seconds)) {
|
|
1497
|
+
return Math.max(0, seconds * 1000);
|
|
1498
|
+
}
|
|
1499
|
+
const dateMs = Date.parse(value);
|
|
1500
|
+
if (!Number.isNaN(dateMs)) {
|
|
1501
|
+
return Math.max(0, dateMs - now);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const retryHintMs = extractRetryHint(undefined, errorMessage);
|
|
1506
|
+
if (retryHintMs !== undefined) {
|
|
1507
|
+
return retryHintMs;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const resetMsMatch = /x-ratelimit-reset-ms\s*[:=]\s*(\d+)/i.exec(errorMessage);
|
|
1511
|
+
if (resetMsMatch) {
|
|
1512
|
+
const resetMs = Number(resetMsMatch[1]);
|
|
1513
|
+
if (!Number.isNaN(resetMs)) {
|
|
1514
|
+
if (resetMs > 1_000_000_000_000) {
|
|
1515
|
+
return Math.max(0, resetMs - now);
|
|
1516
|
+
}
|
|
1517
|
+
return Math.max(0, resetMs);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const resetMatch = /x-ratelimit-reset\s*[:=]\s*(\d+)/i.exec(errorMessage);
|
|
1522
|
+
if (resetMatch) {
|
|
1523
|
+
const resetSeconds = Number(resetMatch[1]);
|
|
1524
|
+
if (!Number.isNaN(resetSeconds)) {
|
|
1525
|
+
if (resetSeconds > 1_000_000_000) {
|
|
1526
|
+
return Math.max(0, resetSeconds * 1000 - now);
|
|
1527
|
+
}
|
|
1528
|
+
return Math.max(0, resetSeconds * 1000);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// Smart Fallback if no exact headers found
|
|
1533
|
+
return undefined;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Handle retryable errors with exponential backoff, credential rotation, and
|
|
1538
|
+
* model-fallback chains. Also entered for NON-retryable errors when a switch
|
|
1539
|
+
* is the recovery (`fireworksFastFallback`, `hardErrorFallback`): then a
|
|
1540
|
+
* successful model switch retries immediately, and a failed switch surfaces
|
|
1541
|
+
* the error without a same-model backoff retry.
|
|
1542
|
+
* @returns true if retry was initiated, false if max retries exceeded or disabled
|
|
1543
|
+
*/
|
|
1544
|
+
async #handleRetryableError(
|
|
1545
|
+
message: AssistantMessage,
|
|
1546
|
+
options?: {
|
|
1547
|
+
allowModelFallback?: boolean;
|
|
1548
|
+
fireworksFastFallback?: boolean;
|
|
1549
|
+
hardErrorFallback?: boolean;
|
|
1550
|
+
preserveFailedTurn?: boolean;
|
|
1551
|
+
},
|
|
1552
|
+
): Promise<boolean> {
|
|
1553
|
+
const retrySettings = this.#host.settings.getGroup("retry");
|
|
1554
|
+
// The Fireworks Fast→base degrade is an intrinsic model-selection safety net,
|
|
1555
|
+
// not a retry loop, so it runs even when the user disabled retries: it switches
|
|
1556
|
+
// the model once and lets the base turn proceed.
|
|
1557
|
+
if (!retrySettings.enabled && !options?.fireworksFastFallback) return false;
|
|
1558
|
+
const classifierRefusal = this.isClassifierRefusal(message);
|
|
1559
|
+
|
|
1560
|
+
const generation = this.#host.promptGeneration();
|
|
1561
|
+
this.#retryAttempt++;
|
|
1562
|
+
|
|
1563
|
+
// Create retry promise on first attempt so waitForRetry() can await it
|
|
1564
|
+
// Ensure only one promise exists (avoid orphaned promises from concurrent calls)
|
|
1565
|
+
if (!this.#retryPromise) {
|
|
1566
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
1567
|
+
this.#retryPromise = promise;
|
|
1568
|
+
this.#retryResolve = resolve;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// All attempts on the current model are spent. Don't fail yet: the
|
|
1572
|
+
// fallback chain below gets one last consult. Credential rotation can
|
|
1573
|
+
// consume the entire budget without the fallback branch ever running
|
|
1574
|
+
// (every rotation sets switchedCredential and skips it), so without
|
|
1575
|
+
// this last resort a provider-wide usage cap never fails over to the
|
|
1576
|
+
// configured chain.
|
|
1577
|
+
const maxRetries = this.#isOpenRouterThinkingStreamClose(message)
|
|
1578
|
+
? Math.min(retrySettings.maxRetries, 1)
|
|
1579
|
+
: retrySettings.maxRetries;
|
|
1580
|
+
const retryBudgetExhausted = this.#retryAttempt > maxRetries;
|
|
1581
|
+
|
|
1582
|
+
const errorMessage = message.errorMessage || "Unknown error";
|
|
1583
|
+
const id = this.#classifyRetryMessage(message);
|
|
1584
|
+
const staleOpenAIResponsesReplayError = AIError.is(id, AIError.Flag.StaleResponsesItem);
|
|
1585
|
+
const recordedUsageLimitOutcome = await this.#usageLimitOutcomes.get(message);
|
|
1586
|
+
const parsedRetryAfterMs = this.#parseRetryAfterMsFromError(errorMessage);
|
|
1587
|
+
let delayMs = staleOpenAIResponsesReplayError
|
|
1588
|
+
? 0
|
|
1589
|
+
: calculateRetryBackoffDelayMs(retrySettings.baseDelayMs, this.#retryAttempt);
|
|
1590
|
+
// Concurrency caps shed-and-backoff (5s) rather than burning a sibling
|
|
1591
|
+
// credential, so the usage-limit rotation branch below is deliberately
|
|
1592
|
+
// skipped for them. Apply the reason-based backoff to the transient
|
|
1593
|
+
// same-model retry path too — otherwise the default exponential base
|
|
1594
|
+
// (≈500ms) re-hits the cap immediately and burns the retry budget while
|
|
1595
|
+
// the concurrency slot stays occupied. A categorical 402 billing cap whose
|
|
1596
|
+
// body merely mentions concurrency is still a usage limit (handled below),
|
|
1597
|
+
// so gate on the flag matching the rotation decision.
|
|
1598
|
+
if (
|
|
1599
|
+
!staleOpenAIResponsesReplayError &&
|
|
1600
|
+
!AIError.is(id, AIError.Flag.UsageLimit) &&
|
|
1601
|
+
parseRateLimitReason(errorMessage) === "CONCURRENT_LIMIT"
|
|
1602
|
+
) {
|
|
1603
|
+
const concurrentBackoffMs = calculateRateLimitBackoffMs("CONCURRENT_LIMIT");
|
|
1604
|
+
if (concurrentBackoffMs > delayMs) delayMs = concurrentBackoffMs;
|
|
1605
|
+
}
|
|
1606
|
+
let switchedCredential = false;
|
|
1607
|
+
let switchedModel = false;
|
|
1608
|
+
// Set when a usage-limit error pinned the wait to credential
|
|
1609
|
+
// availability — suppresses the generic retry-after bump below.
|
|
1610
|
+
let usageLimitWaitMs: number | undefined;
|
|
1611
|
+
|
|
1612
|
+
if (staleOpenAIResponsesReplayError) {
|
|
1613
|
+
this.#host.resetCurrentResponsesProviderSession("stale replay error");
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (!retryBudgetExhausted && !staleOpenAIResponsesReplayError && recordedUsageLimitOutcome) {
|
|
1617
|
+
if (
|
|
1618
|
+
recordedUsageLimitOutcome.switchedCredential ||
|
|
1619
|
+
// Convert the parsed hint to an absolute timestamp NOW, before the
|
|
1620
|
+
// hook's usage IO — a duration re-anchored after slow fetches drifts.
|
|
1621
|
+
(await this.#host.maybeAutoRedeemCodexReset(
|
|
1622
|
+
parsedRetryAfterMs === undefined ? undefined : Date.now() + parsedRetryAfterMs,
|
|
1623
|
+
))
|
|
1624
|
+
) {
|
|
1625
|
+
switchedCredential = true;
|
|
1626
|
+
delayMs = 0;
|
|
1627
|
+
} else {
|
|
1628
|
+
// No sibling credential is usable right now. Wait for whichever
|
|
1629
|
+
// comes first: the provider's retry-after window for the current
|
|
1630
|
+
// account, or the earliest moment a temporarily blocked sibling
|
|
1631
|
+
// frees up (e.g. a 60s post-401 block or a 5-min usage-probe
|
|
1632
|
+
// block) — the next attempt's getApiKey re-ranks and picks it up.
|
|
1633
|
+
// Without this, one short-lived sibling block escalates a
|
|
1634
|
+
// recoverable situation into the provider's multi-hour wait and
|
|
1635
|
+
// trips the fail-fast cap below.
|
|
1636
|
+
usageLimitWaitMs = recordedUsageLimitOutcome.retryAfterMs;
|
|
1637
|
+
if (recordedUsageLimitOutcome.retryAtMs !== undefined) {
|
|
1638
|
+
const siblingWaitMs =
|
|
1639
|
+
Math.max(0, recordedUsageLimitOutcome.retryAtMs - Date.now()) + SIBLING_UNBLOCK_BUFFER_MS;
|
|
1640
|
+
if (siblingWaitMs < usageLimitWaitMs) {
|
|
1641
|
+
usageLimitWaitMs = siblingWaitMs;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
if (usageLimitWaitMs > delayMs) {
|
|
1645
|
+
delayMs = usageLimitWaitMs;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const allowModelFallback = options?.allowModelFallback !== false;
|
|
1651
|
+
const currentModel = this.#host.model();
|
|
1652
|
+
const currentSelector = currentModel
|
|
1653
|
+
? formatRetryFallbackSelector(currentModel, this.#host.thinkingLevel())
|
|
1654
|
+
: undefined;
|
|
1655
|
+
if (!staleOpenAIResponsesReplayError && !switchedCredential && currentSelector) {
|
|
1656
|
+
// A refusal chain stops at the retry budget: the exhausted-attempt
|
|
1657
|
+
// last resort is for provider failures, not classifier decisions.
|
|
1658
|
+
if (allowModelFallback && retrySettings.modelFallback && !(retryBudgetExhausted && classifierRefusal)) {
|
|
1659
|
+
if (!classifierRefusal) {
|
|
1660
|
+
this.noteRetryFallbackCooldown(currentSelector, parsedRetryAfterMs, errorMessage);
|
|
1661
|
+
}
|
|
1662
|
+
switchedModel = await this.#tryRetryModelFallback(currentSelector, { pinFallback: classifierRefusal });
|
|
1663
|
+
}
|
|
1664
|
+
// Auto fallback from a Fireworks Fast variant to its base model. Independent
|
|
1665
|
+
// of the role-fallback setting: it's intrinsic to the Fast contract (speed
|
|
1666
|
+
// best-effort, degrade to Standard on failure) and triggers on hard router
|
|
1667
|
+
// errors the generic retry classifier would otherwise reject.
|
|
1668
|
+
if (!switchedModel && allowModelFallback && options?.fireworksFastFallback) {
|
|
1669
|
+
switchedModel = await this.#tryFireworksFastFallback(currentSelector);
|
|
1670
|
+
}
|
|
1671
|
+
if (switchedModel) {
|
|
1672
|
+
delayMs = 0;
|
|
1673
|
+
} else if (usageLimitWaitMs === undefined && parsedRetryAfterMs && parsedRetryAfterMs > delayMs) {
|
|
1674
|
+
delayMs = parsedRetryAfterMs;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (retryBudgetExhausted) {
|
|
1679
|
+
if (!switchedModel) {
|
|
1680
|
+
await this.persistTerminalEmptyErrorTurn(message);
|
|
1681
|
+
// Max retries exceeded and no fallback model to switch to: emit
|
|
1682
|
+
// final failure and reset.
|
|
1683
|
+
await this.#host.emitSessionEvent({
|
|
1684
|
+
type: "auto_retry_end",
|
|
1685
|
+
success: false,
|
|
1686
|
+
attempt: this.#retryAttempt - 1,
|
|
1687
|
+
finalError: message.errorMessage,
|
|
1688
|
+
});
|
|
1689
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
1690
|
+
this.#retryAttempt = 0;
|
|
1691
|
+
this.resolveRetry(); // Resolve so waitForRetry() completes
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
// The fallback model gets a fresh retry budget — leaving the spent
|
|
1695
|
+
// counter in place would exhaust it again on its first error.
|
|
1696
|
+
this.#retryAttempt = 1;
|
|
1697
|
+
}
|
|
1698
|
+
if (classifierRefusal && !switchedModel) {
|
|
1699
|
+
// A prior attempt in this saga already announced `auto_retry_start`
|
|
1700
|
+
// (retryAttempt was incremented for each call to this method, so > 1
|
|
1701
|
+
// means at least one earlier attempt started the loop) but this
|
|
1702
|
+
// attempt is not going to retry — the saga must close with its own
|
|
1703
|
+
// `auto_retry_end` so subscribers tracking retry-outstanding state
|
|
1704
|
+
// (e.g. suppressing a duplicate error toast) don't stay latched on
|
|
1705
|
+
// an announcement that never resolves.
|
|
1706
|
+
if (this.#retryAttempt > 1) {
|
|
1707
|
+
await this.persistTerminalEmptyErrorTurn(message);
|
|
1708
|
+
await this.#host.emitSessionEvent({
|
|
1709
|
+
type: "auto_retry_end",
|
|
1710
|
+
success: false,
|
|
1711
|
+
attempt: this.#retryAttempt - 1,
|
|
1712
|
+
finalError: errorMessage,
|
|
1713
|
+
});
|
|
1714
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
1715
|
+
}
|
|
1716
|
+
this.#retryAttempt = 0;
|
|
1717
|
+
this.resolveRetry();
|
|
1718
|
+
return false;
|
|
1719
|
+
}
|
|
1720
|
+
// A fallback switch was the whole reason we entered (Fast→base degrade or
|
|
1721
|
+
// a hard-error chain consult) but it could not happen (e.g. no candidate
|
|
1722
|
+
// has a credential). Don't fall through to backing-off and retrying the
|
|
1723
|
+
// failing model for an error the generic classifier wouldn't retry —
|
|
1724
|
+
// surface it instead.
|
|
1725
|
+
if (
|
|
1726
|
+
(options?.fireworksFastFallback || options?.hardErrorFallback) &&
|
|
1727
|
+
!switchedModel &&
|
|
1728
|
+
!this.isRetryableError(message)
|
|
1729
|
+
) {
|
|
1730
|
+
// Same auto_retry_end backstop as the classifier-refusal branch above.
|
|
1731
|
+
if (this.#retryAttempt > 1) {
|
|
1732
|
+
await this.persistTerminalEmptyErrorTurn(message);
|
|
1733
|
+
await this.#host.emitSessionEvent({
|
|
1734
|
+
type: "auto_retry_end",
|
|
1735
|
+
success: false,
|
|
1736
|
+
attempt: this.#retryAttempt - 1,
|
|
1737
|
+
finalError: errorMessage,
|
|
1738
|
+
});
|
|
1739
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
1740
|
+
}
|
|
1741
|
+
this.#retryAttempt = 0;
|
|
1742
|
+
this.resolveRetry();
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// Fail-fast cap: if the provider asks us to wait longer than
|
|
1747
|
+
// retry.maxDelayMs and we have no fallback credential or model to
|
|
1748
|
+
// switch to, surface the error instead of sleeping. Defends against
|
|
1749
|
+
// 3-hour Anthropic rate-limit windows that would otherwise leave a
|
|
1750
|
+
// subagent (or interactive session) silently hung. The original
|
|
1751
|
+
// assistant error message is preserved in agent state so the caller
|
|
1752
|
+
// can act on it.
|
|
1753
|
+
const maxDelayMs = retrySettings.maxDelayMs;
|
|
1754
|
+
if (maxDelayMs > 0 && delayMs > maxDelayMs && !switchedCredential && !switchedModel) {
|
|
1755
|
+
await this.persistTerminalEmptyErrorTurn(message);
|
|
1756
|
+
const attempt = this.#retryAttempt;
|
|
1757
|
+
this.#retryAttempt = 0;
|
|
1758
|
+
await this.#host.emitSessionEvent({
|
|
1759
|
+
type: "auto_retry_end",
|
|
1760
|
+
success: false,
|
|
1761
|
+
attempt,
|
|
1762
|
+
finalError: `Provider requested ${delayMs}ms wait, exceeds retry.maxDelayMs (${maxDelayMs}ms). Original error: ${errorMessage}`,
|
|
1763
|
+
});
|
|
1764
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
1765
|
+
this.resolveRetry();
|
|
1766
|
+
return false;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
await this.#recordPendingRecoveredRetryError(message, id, { switchedCredential, switchedModel, delayMs });
|
|
1770
|
+
|
|
1771
|
+
await this.#host.emitSessionEvent({
|
|
1772
|
+
type: "auto_retry_start",
|
|
1773
|
+
attempt: this.#retryAttempt,
|
|
1774
|
+
maxAttempts: maxRetries,
|
|
1775
|
+
delayMs,
|
|
1776
|
+
errorMessage,
|
|
1777
|
+
errorId: message.errorId,
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
// Resolved stream-stall tools have already emitted results. Keep that failed
|
|
1781
|
+
// turn intact so continuation cannot repeat their side effects.
|
|
1782
|
+
if (!options?.preserveFailedTurn) {
|
|
1783
|
+
this.removeAssistantMessageFromActiveContext(message, "auto-retry");
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// A thinking/response loop retried into identical context loops again. Inject a
|
|
1787
|
+
// hidden redirect so the retried turn sees a directive to break the repeated
|
|
1788
|
+
// pattern instead of re-sampling the same stalled reasoning.
|
|
1789
|
+
this.#maybeInjectThinkingLoopRedirect(id);
|
|
1790
|
+
|
|
1791
|
+
// Wait with exponential backoff (abortable).
|
|
1792
|
+
const retryAbortController = new AbortController();
|
|
1793
|
+
this.#retryAbortController?.abort();
|
|
1794
|
+
this.#retryAbortController = retryAbortController;
|
|
1795
|
+
try {
|
|
1796
|
+
await scheduler.wait(delayMs, { signal: retryAbortController.signal });
|
|
1797
|
+
} catch {
|
|
1798
|
+
if (this.#retryAbortController !== retryAbortController) {
|
|
1799
|
+
return false;
|
|
1800
|
+
}
|
|
1801
|
+
// Aborted during sleep - emit end event so UI can clean up
|
|
1802
|
+
const attempt = this.#retryAttempt;
|
|
1803
|
+
this.#retryAttempt = 0;
|
|
1804
|
+
this.#retryAbortController = undefined;
|
|
1805
|
+
await this.#host.emitSessionEvent({
|
|
1806
|
+
type: "auto_retry_end",
|
|
1807
|
+
success: false,
|
|
1808
|
+
attempt,
|
|
1809
|
+
finalError: "Retry cancelled",
|
|
1810
|
+
});
|
|
1811
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
1812
|
+
this.resolveRetry();
|
|
1813
|
+
return false;
|
|
1814
|
+
}
|
|
1815
|
+
if (this.#retryAbortController === retryAbortController) {
|
|
1816
|
+
this.#retryAbortController = undefined;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// The identity-keyed removal above can miss when a context rebuild
|
|
1820
|
+
// recreated the failed turn's message object between settle and retry
|
|
1821
|
+
// (fresh identity, same failed tail — issue #5382). Agent.continue()
|
|
1822
|
+
// rejects any assistant tail, so a missed removal fails the scheduled
|
|
1823
|
+
// retry locally before a provider request is ever made. Re-check the
|
|
1824
|
+
// tail after the backoff (covering rebuilds during the sleep too) and
|
|
1825
|
+
// strip a still-failed assistant tail by position. Never in
|
|
1826
|
+
// preserveFailedTurn mode — the kept turn ends in synthetic tool
|
|
1827
|
+
// results that continue() accepts — and never once a newer prompt owns
|
|
1828
|
+
// the session.
|
|
1829
|
+
if (!options?.preserveFailedTurn && this.#host.promptGeneration() === generation) {
|
|
1830
|
+
this.#stripFailedAssistantTail();
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// Retry via continue() outside the agent_end event callback chain. A
|
|
1834
|
+
// continuation that still fails locally must close the retry saga —
|
|
1835
|
+
// otherwise auto_retry_end never fires, retryPromise stays pending, and
|
|
1836
|
+
// the in-flight prompt() (and the TUI retry indicator) hang forever.
|
|
1837
|
+
this.#host.scheduleAgentContinue({
|
|
1838
|
+
delayMs: 1,
|
|
1839
|
+
generation,
|
|
1840
|
+
onError: error => void this.#failRetryAfterLocalContinueError(message, error),
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
* Positional backstop for {@link removeAssistantMessageFromActiveContext}:
|
|
1848
|
+
* when the identity check missed, the failed assistant turn is still the
|
|
1849
|
+
* active tail and the scheduled continue() would reject it. An
|
|
1850
|
+
* error/aborted-stopped assistant tail is never legal continuation input
|
|
1851
|
+
* and no recovery path wants it replayed on the wire, so drop it by
|
|
1852
|
+
* position; any healthy tail is left untouched.
|
|
1853
|
+
*/
|
|
1854
|
+
#stripFailedAssistantTail(): void {
|
|
1855
|
+
const messages = this.#host.agent.state.messages;
|
|
1856
|
+
const tail = messages[messages.length - 1];
|
|
1857
|
+
if (tail?.role !== "assistant") return;
|
|
1858
|
+
if (tail.stopReason !== "error" && tail.stopReason !== "aborted") return;
|
|
1859
|
+
logger.debug("agent active context failed assistant tail stripped positionally", {
|
|
1860
|
+
stopReason: tail.stopReason,
|
|
1861
|
+
timestamp: tail.timestamp,
|
|
1862
|
+
});
|
|
1863
|
+
this.#host.agent.replaceMessages(messages.slice(0, -1));
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* Close the retry saga when the scheduled continue() failed locally (no
|
|
1868
|
+
* provider request was made). Mirrors the other retry dead-ends: emit the
|
|
1869
|
+
* closing `auto_retry_end` so subscribers stop showing retry progress, and
|
|
1870
|
+
* resolve the retry promise so the in-flight prompt() unwinds (issue #5382).
|
|
1871
|
+
*/
|
|
1872
|
+
async #failRetryAfterLocalContinueError(message: AssistantMessage, error: unknown): Promise<void> {
|
|
1873
|
+
if (this.#retryAttempt === 0) return;
|
|
1874
|
+
const attempt = this.#retryAttempt;
|
|
1875
|
+
this.#retryAttempt = 0;
|
|
1876
|
+
const localError = error instanceof Error ? error.message : String(error);
|
|
1877
|
+
await this.persistTerminalEmptyErrorTurn(message);
|
|
1878
|
+
await this.#host.emitSessionEvent({
|
|
1879
|
+
type: "auto_retry_end",
|
|
1880
|
+
success: false,
|
|
1881
|
+
attempt,
|
|
1882
|
+
finalError: `Retry continuation failed locally: ${localError}. Original error: ${message.errorMessage ?? "Unknown error"}`,
|
|
1883
|
+
});
|
|
1884
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
1885
|
+
this.resolveRetry();
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
/**
|
|
1889
|
+
* Inject a hidden redirect notice when a thinking/response loop is being retried, so
|
|
1890
|
+
* the retried turn carries an instruction to break the repeated pattern instead of
|
|
1891
|
+
* re-sampling the same stalled context. Injected on every {@link AIError.Flag.ThinkingLoop}
|
|
1892
|
+
* retry (the failed assistant is dropped each attempt, so the notice does not accumulate
|
|
1893
|
+
* unboundedly). No-op unless `id` carries the ThinkingLoop flag and the loop guard is
|
|
1894
|
+
* enabled. The notice is generic on purpose — the detector's detail can quote raw model
|
|
1895
|
+
* text, which must not be interpolated into a higher-priority developer message.
|
|
1896
|
+
*/
|
|
1897
|
+
#maybeInjectThinkingLoopRedirect(id: number): void {
|
|
1898
|
+
if (!AIError.is(id, AIError.Flag.ThinkingLoop)) return;
|
|
1899
|
+
if (this.#host.settings.get("model.loopGuard.enabled") !== true) return;
|
|
1900
|
+
this.#host.agent.appendMessage({
|
|
1901
|
+
role: "custom",
|
|
1902
|
+
customType: THINKING_LOOP_REDIRECT_TYPE,
|
|
1903
|
+
content: thinkingLoopRedirectTemplate,
|
|
1904
|
+
display: false,
|
|
1905
|
+
attribution: "agent",
|
|
1906
|
+
timestamp: Date.now(),
|
|
1907
|
+
});
|
|
1908
|
+
this.#host.sessionManager.appendCustomMessageEntry(
|
|
1909
|
+
THINKING_LOOP_REDIRECT_TYPE,
|
|
1910
|
+
thinkingLoopRedirectTemplate,
|
|
1911
|
+
false,
|
|
1912
|
+
undefined,
|
|
1913
|
+
"agent",
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
/**
|
|
1918
|
+
* Cancel in-progress retry.
|
|
1919
|
+
*/
|
|
1920
|
+
abortRetry(): void {
|
|
1921
|
+
this.#retryAbortController?.abort();
|
|
1922
|
+
// Note: _retryAttempt is reset in the catch block of _autoRetry
|
|
1923
|
+
this.resolveRetry();
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
async #promptAgentWithIdleRetry(messages: AgentMessage[], options?: { toolChoice?: ToolChoice }): Promise<void> {
|
|
1927
|
+
const deadline = Date.now() + 30_000;
|
|
1928
|
+
for (;;) {
|
|
1929
|
+
try {
|
|
1930
|
+
await this.#host.agent.prompt(messages, options);
|
|
1931
|
+
return;
|
|
1932
|
+
} catch (err) {
|
|
1933
|
+
if (!(err instanceof AgentBusyError)) {
|
|
1934
|
+
throw err;
|
|
1935
|
+
}
|
|
1936
|
+
if (Date.now() >= deadline) {
|
|
1937
|
+
throw new Error("Timed out waiting for prior agent run to finish before prompting.");
|
|
1938
|
+
}
|
|
1939
|
+
await this.#host.agent.waitForIdle();
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
/** Whether auto-retry is currently in progress */
|
|
1945
|
+
get isRetrying(): boolean {
|
|
1946
|
+
return this.#retryPromise !== undefined;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/** Whether auto-retry is enabled */
|
|
1950
|
+
get autoRetryEnabled(): boolean {
|
|
1951
|
+
return this.#host.settings.get("retry.enabled") ?? true;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
/**
|
|
1955
|
+
* Toggle auto-retry setting.
|
|
1956
|
+
*/
|
|
1957
|
+
setAutoRetryEnabled(enabled: boolean): void {
|
|
1958
|
+
this.#host.settings.set("retry.enabled", enabled);
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Manually retry the last failed assistant turn.
|
|
1962
|
+
* Removes the error message from active agent state when present and
|
|
1963
|
+
* re-attempts with a fresh retry budget.
|
|
1964
|
+
*
|
|
1965
|
+
* A stream that stalls or aborts mid-tool-call ends the turn with
|
|
1966
|
+
* `stopReason: "error" | "aborted"` and then appends one synthetic
|
|
1967
|
+
* {@link isSyntheticToolResultMessage tool_result} per emitted tool call to
|
|
1968
|
+
* preserve the provider's tool_use/tool_result pairing (see
|
|
1969
|
+
* `createAbortedToolResult` in `agent-loop.ts`). Those placeholders trail the
|
|
1970
|
+
* failed assistant turn, so the retry lookback walks back over them before
|
|
1971
|
+
* checking the assistant message; it strips both the placeholders and the
|
|
1972
|
+
* failed turn before re-attempting.
|
|
1973
|
+
*
|
|
1974
|
+
* A restored session deliberately omits failed assistant turns from provider
|
|
1975
|
+
* context. In that case, the persisted display transcript remains the source
|
|
1976
|
+
* of truth for whether the current branch has a retryable failed tail.
|
|
1977
|
+
*
|
|
1978
|
+
* @returns true if retry was initiated, false if no failed turn to retry or agent is busy
|
|
1979
|
+
*/
|
|
1980
|
+
async retry(): Promise<boolean> {
|
|
1981
|
+
if (this.#host.isStreaming() || this.#host.isCompacting() || this.isRetrying) return false;
|
|
1982
|
+
|
|
1983
|
+
const messages = this.#host.agent.state.messages;
|
|
1984
|
+
const activeTurnEnd = retryableAssistantTurnEnd(messages);
|
|
1985
|
+
if (activeTurnEnd !== undefined) {
|
|
1986
|
+
// Remove the failed/aborted assistant message plus its synthetic tool
|
|
1987
|
+
// results (same as auto-retry does before re-attempting).
|
|
1988
|
+
this.#host.agent.replaceMessages(messages.slice(0, activeTurnEnd - 1));
|
|
1989
|
+
} else {
|
|
1990
|
+
// A restored session already dropped the failed assistant turn (and its
|
|
1991
|
+
// paired synthetic tool results) from provider context, so the persisted
|
|
1992
|
+
// display transcript is the source of truth for a retryable failed tail.
|
|
1993
|
+
const transcriptMessages = this.#host.sessionManager.buildSessionContext({ transcript: true }).messages;
|
|
1994
|
+
if (retryableAssistantTurnEnd(transcriptMessages) === undefined) return false;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// Reset retry budget for a fresh attempt
|
|
1998
|
+
this.#retryAttempt = 0;
|
|
1999
|
+
|
|
2000
|
+
// Re-attempt the turn
|
|
2001
|
+
this.#host.scheduleAgentContinue({ delayMs: 1 });
|
|
2002
|
+
|
|
2003
|
+
return true;
|
|
2004
|
+
}
|
|
2005
|
+
}
|