@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.
Files changed (1593) hide show
  1. package/CHANGELOG.md +14861 -0
  2. package/README.md +35 -0
  3. package/dist/CHANGELOG-chtdxh1z.md +14861 -0
  4. package/dist/cli.js +20483 -0
  5. package/dist/template-c2hyaytt.js +1653 -0
  6. package/dist/template-f8wx9vfn.css +1355 -0
  7. package/dist/template-qat058wr.html +55 -0
  8. package/dist/tool-views.generated-jdfmzwmn.js +35 -0
  9. package/examples/README.md +21 -0
  10. package/examples/custom-tools/README.md +104 -0
  11. package/examples/custom-tools/hello/index.ts +20 -0
  12. package/examples/extensions/README.md +142 -0
  13. package/examples/extensions/api-demo.ts +78 -0
  14. package/examples/extensions/chalk-logger.ts +25 -0
  15. package/examples/extensions/hello.ts +31 -0
  16. package/examples/extensions/pirate.ts +43 -0
  17. package/examples/extensions/plan-mode.ts +549 -0
  18. package/examples/extensions/reload-runtime.ts +38 -0
  19. package/examples/extensions/thinking-note.ts +13 -0
  20. package/examples/extensions/tools.ts +145 -0
  21. package/examples/extensions/with-deps/index.ts +36 -0
  22. package/examples/extensions/with-deps/package-lock.json +31 -0
  23. package/examples/extensions/with-deps/package.json +17 -0
  24. package/examples/hooks/README.md +56 -0
  25. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  26. package/examples/hooks/confirm-destructive.ts +58 -0
  27. package/examples/hooks/custom-compaction.ts +115 -0
  28. package/examples/hooks/dirty-repo-guard.ts +51 -0
  29. package/examples/hooks/file-trigger.ts +40 -0
  30. package/examples/hooks/git-checkpoint.ts +52 -0
  31. package/examples/hooks/handoff.ts +149 -0
  32. package/examples/hooks/permission-gate.ts +33 -0
  33. package/examples/hooks/protected-paths.ts +29 -0
  34. package/examples/hooks/qna.ts +118 -0
  35. package/examples/hooks/status-line.ts +39 -0
  36. package/examples/sdk/01-minimal.ts +21 -0
  37. package/examples/sdk/02-custom-model.ts +49 -0
  38. package/examples/sdk/03-custom-prompt.ts +46 -0
  39. package/examples/sdk/04-skills.ts +43 -0
  40. package/examples/sdk/06-extensions.ts +82 -0
  41. package/examples/sdk/06-hooks.ts +61 -0
  42. package/examples/sdk/07-context-files.ts +35 -0
  43. package/examples/sdk/08-prompt-templates.ts +41 -0
  44. package/examples/sdk/08-slash-commands.ts +46 -0
  45. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  46. package/examples/sdk/11-sessions.ts +47 -0
  47. package/examples/sdk/12-redis-sessions.ts +54 -0
  48. package/examples/sdk/13-sql-sessions.ts +61 -0
  49. package/examples/sdk/README.md +169 -0
  50. package/package.json +587 -0
  51. package/scripts/bench-guard.ts +71 -0
  52. package/scripts/bench-title-models.ts +332 -0
  53. package/scripts/build-binary.ts +117 -0
  54. package/scripts/bundle-dist.ts +123 -0
  55. package/scripts/compile-binary.ts +69 -0
  56. package/scripts/embed-mupdf-wasm.ts +67 -0
  57. package/scripts/format-prompts.ts +68 -0
  58. package/scripts/generate-aria-snapshot.ts +134 -0
  59. package/scripts/generate-docs-index.ts +58 -0
  60. package/scripts/generate-share-viewer.ts +34 -0
  61. package/scripts/legacy-pi-virtual-module.ts +205 -0
  62. package/scripts/measure-prompt-tokens.ts +63 -0
  63. package/scripts/omp +51 -0
  64. package/scripts/omp.ts +19 -0
  65. package/scripts/security-compare.ts +40 -0
  66. package/src/advisor/advise-tool.ts +234 -0
  67. package/src/advisor/config.ts +341 -0
  68. package/src/advisor/emission-guard.ts +172 -0
  69. package/src/advisor/index.ts +6 -0
  70. package/src/advisor/runtime.ts +1412 -0
  71. package/src/advisor/transcript-recorder.ts +215 -0
  72. package/src/advisor/watchdog.ts +135 -0
  73. package/src/async/index.ts +1 -0
  74. package/src/async/job-manager.ts +847 -0
  75. package/src/auto-thinking/classifier.ts +220 -0
  76. package/src/autolearn/controller.ts +152 -0
  77. package/src/autolearn/managed-skills.ts +255 -0
  78. package/src/autoresearch/command-resume.md +14 -0
  79. package/src/autoresearch/dashboard.ts +436 -0
  80. package/src/autoresearch/git.ts +331 -0
  81. package/src/autoresearch/helpers.ts +218 -0
  82. package/src/autoresearch/index.ts +541 -0
  83. package/src/autoresearch/prompt-setup.md +43 -0
  84. package/src/autoresearch/prompt.md +103 -0
  85. package/src/autoresearch/resume-message.md +10 -0
  86. package/src/autoresearch/state.ts +273 -0
  87. package/src/autoresearch/storage.ts +700 -0
  88. package/src/autoresearch/tools/init-experiment.ts +268 -0
  89. package/src/autoresearch/tools/log-experiment.ts +520 -0
  90. package/src/autoresearch/tools/run-experiment.ts +407 -0
  91. package/src/autoresearch/tools/update-notes.ts +109 -0
  92. package/src/autoresearch/types.ts +168 -0
  93. package/src/capability/context-file.ts +44 -0
  94. package/src/capability/extension-module.ts +34 -0
  95. package/src/capability/extension.ts +47 -0
  96. package/src/capability/fs.ts +117 -0
  97. package/src/capability/hook.ts +40 -0
  98. package/src/capability/index.ts +467 -0
  99. package/src/capability/instruction.ts +37 -0
  100. package/src/capability/mcp.ts +115 -0
  101. package/src/capability/prompt.ts +35 -0
  102. package/src/capability/rule-buckets.ts +66 -0
  103. package/src/capability/rule.ts +298 -0
  104. package/src/capability/settings.ts +34 -0
  105. package/src/capability/skill.ts +69 -0
  106. package/src/capability/slash-command.ts +40 -0
  107. package/src/capability/ssh.ts +41 -0
  108. package/src/capability/system-prompt.ts +34 -0
  109. package/src/capability/tool.ts +38 -0
  110. package/src/capability/types.ts +187 -0
  111. package/src/cleanse/agent.ts +226 -0
  112. package/src/cleanse/balance.ts +79 -0
  113. package/src/cleanse/checkers.ts +996 -0
  114. package/src/cleanse/index.ts +190 -0
  115. package/src/cleanse/loop.ts +51 -0
  116. package/src/cleanse/parsers.ts +726 -0
  117. package/src/cleanse/progress.ts +50 -0
  118. package/src/cleanse/prompts/assignment.md +47 -0
  119. package/src/cleanse/types.ts +72 -0
  120. package/src/cli/agents-cli.ts +138 -0
  121. package/src/cli/args.ts +368 -0
  122. package/src/cli/auth-broker-cli.ts +940 -0
  123. package/src/cli/auth-gateway-cli.ts +674 -0
  124. package/src/cli/bench-cli.ts +990 -0
  125. package/src/cli/browser-relay-cli.ts +119 -0
  126. package/src/cli/classify-install-target.ts +76 -0
  127. package/src/cli/claude-trace-cli.ts +795 -0
  128. package/src/cli/command-help.ts +107 -0
  129. package/src/cli/commands/init-xdg.ts +27 -0
  130. package/src/cli/completion-gen.ts +550 -0
  131. package/src/cli/config-cli.ts +459 -0
  132. package/src/cli/dry-balance-cli.ts +864 -0
  133. package/src/cli/extension-flags.ts +48 -0
  134. package/src/cli/file-processor.ts +132 -0
  135. package/src/cli/flag-tables.ts +367 -0
  136. package/src/cli/gallery-cli.ts +272 -0
  137. package/src/cli/gallery-fixtures/agentic.ts +420 -0
  138. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  139. package/src/cli/gallery-fixtures/edit.ts +254 -0
  140. package/src/cli/gallery-fixtures/fs.ts +245 -0
  141. package/src/cli/gallery-fixtures/index.ts +40 -0
  142. package/src/cli/gallery-fixtures/interaction.ts +46 -0
  143. package/src/cli/gallery-fixtures/memory.ts +81 -0
  144. package/src/cli/gallery-fixtures/misc.ts +177 -0
  145. package/src/cli/gallery-fixtures/search.ts +135 -0
  146. package/src/cli/gallery-fixtures/shell.ts +241 -0
  147. package/src/cli/gallery-fixtures/types.ts +57 -0
  148. package/src/cli/gallery-fixtures/web.ts +158 -0
  149. package/src/cli/gallery-screenshot.ts +279 -0
  150. package/src/cli/gc-cli.ts +1566 -0
  151. package/src/cli/grep-cli.ts +161 -0
  152. package/src/cli/grievances-cli.ts +256 -0
  153. package/src/cli/help-extra.ts +89 -0
  154. package/src/cli/initial-message.ts +58 -0
  155. package/src/cli/models-cli.ts +385 -0
  156. package/src/cli/plugin-cli.ts +996 -0
  157. package/src/cli/profile-alias.ts +369 -0
  158. package/src/cli/profile-bootstrap.ts +233 -0
  159. package/src/cli/read-cli.ts +99 -0
  160. package/src/cli/session-picker.ts +110 -0
  161. package/src/cli/setup-cli.ts +312 -0
  162. package/src/cli/setup-model-picker.ts +43 -0
  163. package/src/cli/shell-cli.ts +176 -0
  164. package/src/cli/ssh-cli.ts +179 -0
  165. package/src/cli/startup-cwd.ts +58 -0
  166. package/src/cli/stats-cli.ts +229 -0
  167. package/src/cli/thinking-levels.ts +7 -0
  168. package/src/cli/tiny-models-cli.ts +153 -0
  169. package/src/cli/ttsr-cli.ts +1013 -0
  170. package/src/cli/update-cli.ts +1184 -0
  171. package/src/cli/usage-cli.ts +1084 -0
  172. package/src/cli/usage-error.ts +7 -0
  173. package/src/cli/web-search-cli.ts +144 -0
  174. package/src/cli/worktree-cli.ts +311 -0
  175. package/src/cli-commands.ts +308 -0
  176. package/src/cli.ts +428 -0
  177. package/src/collab/crypto.ts +63 -0
  178. package/src/collab/display-name.ts +13 -0
  179. package/src/collab/guest.ts +764 -0
  180. package/src/collab/host.ts +692 -0
  181. package/src/collab/protocol.ts +296 -0
  182. package/src/collab/relay-client.ts +282 -0
  183. package/src/collab/replication-shrink.ts +111 -0
  184. package/src/commands/acp.ts +35 -0
  185. package/src/commands/agents.ts +58 -0
  186. package/src/commands/auth-broker.ts +100 -0
  187. package/src/commands/auth-gateway.ts +70 -0
  188. package/src/commands/bench.ts +64 -0
  189. package/src/commands/browser-relay.ts +53 -0
  190. package/src/commands/cleanse.ts +45 -0
  191. package/src/commands/commit.ts +47 -0
  192. package/src/commands/complete.ts +67 -0
  193. package/src/commands/completions.ts +61 -0
  194. package/src/commands/config.ts +52 -0
  195. package/src/commands/dry-balance.ts +43 -0
  196. package/src/commands/gallery.ts +61 -0
  197. package/src/commands/gc.ts +47 -0
  198. package/src/commands/grep.ts +49 -0
  199. package/src/commands/grievances.ts +52 -0
  200. package/src/commands/install.ts +107 -0
  201. package/src/commands/join.ts +40 -0
  202. package/src/commands/launch-help.ts +116 -0
  203. package/src/commands/launch.ts +34 -0
  204. package/src/commands/models.ts +61 -0
  205. package/src/commands/plugin.ts +79 -0
  206. package/src/commands/read.ts +39 -0
  207. package/src/commands/say.ts +146 -0
  208. package/src/commands/setup.ts +68 -0
  209. package/src/commands/share.ts +71 -0
  210. package/src/commands/shell.ts +30 -0
  211. package/src/commands/ssh.ts +61 -0
  212. package/src/commands/stats.ts +30 -0
  213. package/src/commands/tiny-models.ts +36 -0
  214. package/src/commands/token.ts +164 -0
  215. package/src/commands/ttsr.ts +125 -0
  216. package/src/commands/update.ts +34 -0
  217. package/src/commands/usage.ts +55 -0
  218. package/src/commands/web-search.ts +43 -0
  219. package/src/commands/worktree.ts +63 -0
  220. package/src/commit/agentic/agent.ts +322 -0
  221. package/src/commit/agentic/fallback.ts +96 -0
  222. package/src/commit/agentic/index.ts +383 -0
  223. package/src/commit/agentic/lock-files.ts +107 -0
  224. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  225. package/src/commit/agentic/prompts/session-user.md +25 -0
  226. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  227. package/src/commit/agentic/prompts/system.md +38 -0
  228. package/src/commit/agentic/state.ts +60 -0
  229. package/src/commit/agentic/tools/analyze-file.ts +148 -0
  230. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  231. package/src/commit/agentic/tools/git-hunk.ts +52 -0
  232. package/src/commit/agentic/tools/git-overview.ts +62 -0
  233. package/src/commit/agentic/tools/index.ts +54 -0
  234. package/src/commit/agentic/tools/propose-changelog.ts +147 -0
  235. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  236. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  237. package/src/commit/agentic/tools/schemas.ts +11 -0
  238. package/src/commit/agentic/tools/split-commit.ts +241 -0
  239. package/src/commit/agentic/topo-sort.ts +44 -0
  240. package/src/commit/agentic/trivial.ts +51 -0
  241. package/src/commit/agentic/validation.ts +183 -0
  242. package/src/commit/analysis/conventional.ts +64 -0
  243. package/src/commit/analysis/index.ts +4 -0
  244. package/src/commit/analysis/scope.ts +242 -0
  245. package/src/commit/analysis/summary.ts +107 -0
  246. package/src/commit/analysis/validation.ts +66 -0
  247. package/src/commit/changelog/detect.ts +40 -0
  248. package/src/commit/changelog/generate.ts +101 -0
  249. package/src/commit/changelog/index.ts +234 -0
  250. package/src/commit/changelog/parse.ts +44 -0
  251. package/src/commit/cli.ts +85 -0
  252. package/src/commit/git/diff.ts +148 -0
  253. package/src/commit/index.ts +5 -0
  254. package/src/commit/map-reduce/index.ts +69 -0
  255. package/src/commit/map-reduce/map-phase.ts +193 -0
  256. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  257. package/src/commit/map-reduce/utils.ts +9 -0
  258. package/src/commit/message.ts +11 -0
  259. package/src/commit/model-selection.ts +95 -0
  260. package/src/commit/pipeline.ts +244 -0
  261. package/src/commit/prompts/analysis-system.md +148 -0
  262. package/src/commit/prompts/analysis-user.md +38 -0
  263. package/src/commit/prompts/changelog-system.md +50 -0
  264. package/src/commit/prompts/changelog-user.md +18 -0
  265. package/src/commit/prompts/file-observer-system.md +24 -0
  266. package/src/commit/prompts/file-observer-user.md +8 -0
  267. package/src/commit/prompts/reduce-system.md +50 -0
  268. package/src/commit/prompts/reduce-user.md +17 -0
  269. package/src/commit/prompts/summary-retry.md +3 -0
  270. package/src/commit/prompts/summary-system.md +38 -0
  271. package/src/commit/prompts/summary-user.md +13 -0
  272. package/src/commit/prompts/types-description.md +2 -0
  273. package/src/commit/shared-llm.ts +70 -0
  274. package/src/commit/types.ts +118 -0
  275. package/src/commit/utils/exclusions.ts +42 -0
  276. package/src/commit/utils.ts +58 -0
  277. package/src/config/api-key-resolver.ts +81 -0
  278. package/src/config/append-only-context-mode.ts +76 -0
  279. package/src/config/config-file.ts +347 -0
  280. package/src/config/inline-tool-descriptors-mode.ts +26 -0
  281. package/src/config/keybindings.ts +703 -0
  282. package/src/config/mcp-schema.json +252 -0
  283. package/src/config/model-discovery.ts +1077 -0
  284. package/src/config/model-registry.ts +2822 -0
  285. package/src/config/model-resolver.ts +2115 -0
  286. package/src/config/model-roles.ts +113 -0
  287. package/src/config/models-config-schema-bundle.ts +315 -0
  288. package/src/config/models-config-schema.ts +14 -0
  289. package/src/config/models-config.ts +130 -0
  290. package/src/config/prompt-templates.ts +205 -0
  291. package/src/config/provider-globals.ts +25 -0
  292. package/src/config/resolve-config-value.ts +95 -0
  293. package/src/config/service-tier.ts +146 -0
  294. package/src/config/settings-schema.ts +5887 -0
  295. package/src/config/settings.ts +2394 -0
  296. package/src/config.ts +242 -0
  297. package/src/cursor-bridge-tools.ts +81 -0
  298. package/src/cursor.ts +960 -0
  299. package/src/dap/client.ts +1043 -0
  300. package/src/dap/config.ts +480 -0
  301. package/src/dap/defaults.json +212 -0
  302. package/src/dap/index.ts +4 -0
  303. package/src/dap/session.ts +1841 -0
  304. package/src/dap/types.ts +611 -0
  305. package/src/debug/index.ts +584 -0
  306. package/src/debug/log-formatting.ts +58 -0
  307. package/src/debug/log-viewer.ts +966 -0
  308. package/src/debug/profiler.ts +168 -0
  309. package/src/debug/protocol-probe.ts +267 -0
  310. package/src/debug/raw-sse-buffer.ts +421 -0
  311. package/src/debug/raw-sse.ts +312 -0
  312. package/src/debug/remote-debugger.ts +151 -0
  313. package/src/debug/report-bundle.ts +411 -0
  314. package/src/debug/system-info.ts +111 -0
  315. package/src/debug/terminal-info.ts +124 -0
  316. package/src/discovery/agent-plugin-format.ts +551 -0
  317. package/src/discovery/agent-plugins.ts +341 -0
  318. package/src/discovery/agents-md.ts +67 -0
  319. package/src/discovery/agents.ts +300 -0
  320. package/src/discovery/at-imports.ts +273 -0
  321. package/src/discovery/builtin-defaults.ts +39 -0
  322. package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
  323. package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
  324. package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
  325. package/src/discovery/builtin-rules/go-ioutil.md +37 -0
  326. package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
  327. package/src/discovery/builtin-rules/go-new-expr.md +44 -0
  328. package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
  329. package/src/discovery/builtin-rules/go-range-int.md +45 -0
  330. package/src/discovery/builtin-rules/index.ts +74 -0
  331. package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
  332. package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
  333. package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
  334. package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
  335. package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
  336. package/src/discovery/builtin-rules/rs-result-type.md +20 -0
  337. package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
  338. package/src/discovery/builtin-rules/ts-import-type.md +43 -0
  339. package/src/discovery/builtin-rules/ts-no-any.md +66 -0
  340. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
  341. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
  342. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
  343. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  344. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  345. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  346. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  347. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
  348. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  349. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  350. package/src/discovery/builtin.ts +945 -0
  351. package/src/discovery/claude-plugins.ts +640 -0
  352. package/src/discovery/claude.ts +591 -0
  353. package/src/discovery/cline.ts +83 -0
  354. package/src/discovery/codex.ts +553 -0
  355. package/src/discovery/contained-path.ts +78 -0
  356. package/src/discovery/cursor.ts +223 -0
  357. package/src/discovery/gemini.ts +386 -0
  358. package/src/discovery/github.ts +337 -0
  359. package/src/discovery/helpers.ts +1183 -0
  360. package/src/discovery/index.ts +82 -0
  361. package/src/discovery/mcp-json.ts +182 -0
  362. package/src/discovery/omp-extension-roots.ts +272 -0
  363. package/src/discovery/omp-plugins.ts +409 -0
  364. package/src/discovery/opencode.ts +441 -0
  365. package/src/discovery/plugin-dir-roots.ts +28 -0
  366. package/src/discovery/ssh.ts +153 -0
  367. package/src/discovery/substitute-plugin-root.ts +77 -0
  368. package/src/discovery/vscode.ts +106 -0
  369. package/src/discovery/windsurf.ts +149 -0
  370. package/src/edit/apply-patch/index.ts +87 -0
  371. package/src/edit/apply-patch/parser.ts +174 -0
  372. package/src/edit/diff.ts +1008 -0
  373. package/src/edit/edit-clipboard.ts +23 -0
  374. package/src/edit/file-snapshot-store.ts +148 -0
  375. package/src/edit/hashline/block-resolver.ts +33 -0
  376. package/src/edit/hashline/diff.ts +402 -0
  377. package/src/edit/hashline/execute.ts +313 -0
  378. package/src/edit/hashline/filesystem.ts +247 -0
  379. package/src/edit/hashline/index.ts +5 -0
  380. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  381. package/src/edit/hashline/params.ts +12 -0
  382. package/src/edit/index.ts +699 -0
  383. package/src/edit/modes/apply-patch.lark +19 -0
  384. package/src/edit/modes/apply-patch.ts +53 -0
  385. package/src/edit/modes/patch.ts +1958 -0
  386. package/src/edit/modes/replace.ts +1191 -0
  387. package/src/edit/normalize.ts +345 -0
  388. package/src/edit/notebook.ts +242 -0
  389. package/src/edit/read-file.ts +25 -0
  390. package/src/edit/renderer.ts +998 -0
  391. package/src/edit/snapshot-details.ts +77 -0
  392. package/src/edit/streaming.ts +710 -0
  393. package/src/eval/agent-bridge.ts +224 -0
  394. package/src/eval/backend-helpers.ts +48 -0
  395. package/src/eval/backend.ts +71 -0
  396. package/src/eval/bridge-timeout.ts +65 -0
  397. package/src/eval/budget-bridge.ts +48 -0
  398. package/src/eval/completion-bridge.ts +212 -0
  399. package/src/eval/concurrency-bridge.ts +34 -0
  400. package/src/eval/executor-base.ts +562 -0
  401. package/src/eval/idle-timeout.ts +91 -0
  402. package/src/eval/index.ts +6 -0
  403. package/src/eval/jl/executor.ts +551 -0
  404. package/src/eval/jl/index.ts +54 -0
  405. package/src/eval/jl/kernel.ts +236 -0
  406. package/src/eval/jl/prelude.jl +736 -0
  407. package/src/eval/jl/prelude.ts +3 -0
  408. package/src/eval/jl/runner.jl +666 -0
  409. package/src/eval/jl/runtime.ts +118 -0
  410. package/src/eval/js/context-manager.ts +872 -0
  411. package/src/eval/js/executor.ts +178 -0
  412. package/src/eval/js/index.ts +41 -0
  413. package/src/eval/js/process-entry.ts +31 -0
  414. package/src/eval/js/shared/helpers.ts +170 -0
  415. package/src/eval/js/shared/indirect-eval.ts +30 -0
  416. package/src/eval/js/shared/local-module-loader.ts +364 -0
  417. package/src/eval/js/shared/prelude.ts +2 -0
  418. package/src/eval/js/shared/prelude.txt +294 -0
  419. package/src/eval/js/shared/rewrite-imports.ts +550 -0
  420. package/src/eval/js/shared/runtime.ts +590 -0
  421. package/src/eval/js/shared/types.ts +18 -0
  422. package/src/eval/js/tool-bridge.ts +163 -0
  423. package/src/eval/js/worker-core.ts +380 -0
  424. package/src/eval/js/worker-entry.ts +37 -0
  425. package/src/eval/js/worker-protocol.ts +47 -0
  426. package/src/eval/kernel-base.ts +569 -0
  427. package/src/eval/py/display.ts +71 -0
  428. package/src/eval/py/executor.ts +643 -0
  429. package/src/eval/py/index.ts +57 -0
  430. package/src/eval/py/kernel.ts +235 -0
  431. package/src/eval/py/prelude.py +673 -0
  432. package/src/eval/py/prelude.ts +3 -0
  433. package/src/eval/py/runner.py +1425 -0
  434. package/src/eval/py/runtime.ts +276 -0
  435. package/src/eval/py/spawn-options.ts +134 -0
  436. package/src/eval/py/tool-bridge.ts +201 -0
  437. package/src/eval/rb/executor.ts +511 -0
  438. package/src/eval/rb/index.ts +54 -0
  439. package/src/eval/rb/kernel.ts +231 -0
  440. package/src/eval/rb/prelude.rb +551 -0
  441. package/src/eval/rb/prelude.ts +3 -0
  442. package/src/eval/rb/runner.rb +581 -0
  443. package/src/eval/rb/runtime.ts +132 -0
  444. package/src/eval/runtime-env.ts +104 -0
  445. package/src/eval/session-id.ts +8 -0
  446. package/src/eval/types.ts +48 -0
  447. package/src/exa/index.ts +2 -0
  448. package/src/exa/mcp-client.ts +370 -0
  449. package/src/exa/types.ts +69 -0
  450. package/src/exec/bash-executor.ts +627 -0
  451. package/src/exec/direnv.ts +145 -0
  452. package/src/exec/exec.ts +53 -0
  453. package/src/exec/non-interactive-env.ts +118 -0
  454. package/src/export/custom-share.ts +65 -0
  455. package/src/export/html/args.ts +20 -0
  456. package/src/export/html/index.ts +320 -0
  457. package/src/export/html/share-loader.js +102 -0
  458. package/src/export/html/template.css +1355 -0
  459. package/src/export/html/template.html +55 -0
  460. package/src/export/html/template.js +1653 -0
  461. package/src/export/html/tool-views.generated.js +35 -0
  462. package/src/export/html/vendor/highlight.min.js +1213 -0
  463. package/src/export/html/vendor/marked.min.js +6 -0
  464. package/src/export/html/web-palette.ts +126 -0
  465. package/src/export/share.ts +686 -0
  466. package/src/export/ttsr.ts +590 -0
  467. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  468. package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
  469. package/src/extensibility/custom-commands/index.ts +2 -0
  470. package/src/extensibility/custom-commands/loader.ts +242 -0
  471. package/src/extensibility/custom-commands/types.ts +119 -0
  472. package/src/extensibility/custom-tools/index.ts +7 -0
  473. package/src/extensibility/custom-tools/loader.ts +301 -0
  474. package/src/extensibility/custom-tools/types.ts +287 -0
  475. package/src/extensibility/custom-tools/wrapper.ts +50 -0
  476. package/src/extensibility/extensions/compact-handler.ts +40 -0
  477. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  478. package/src/extensibility/extensions/index.ts +16 -0
  479. package/src/extensibility/extensions/load-errors.ts +13 -0
  480. package/src/extensibility/extensions/loader.ts +663 -0
  481. package/src/extensibility/extensions/managed-timers.ts +83 -0
  482. package/src/extensibility/extensions/model-api.ts +39 -0
  483. package/src/extensibility/extensions/runner.ts +1396 -0
  484. package/src/extensibility/extensions/types.ts +1603 -0
  485. package/src/extensibility/extensions/wrapper.ts +395 -0
  486. package/src/extensibility/hooks/index.ts +6 -0
  487. package/src/extensibility/hooks/loader.ts +243 -0
  488. package/src/extensibility/hooks/runner.ts +425 -0
  489. package/src/extensibility/hooks/tool-wrapper.ts +124 -0
  490. package/src/extensibility/hooks/types.ts +612 -0
  491. package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
  492. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
  493. package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
  494. package/src/extensibility/legacy-typebox.ts +101 -0
  495. package/src/extensibility/plugins/bun-git-cache.ts +91 -0
  496. package/src/extensibility/plugins/doctor.ts +65 -0
  497. package/src/extensibility/plugins/git-url.ts +367 -0
  498. package/src/extensibility/plugins/index.ts +9 -0
  499. package/src/extensibility/plugins/installer.ts +201 -0
  500. package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
  501. package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
  502. package/src/extensibility/plugins/loader.ts +535 -0
  503. package/src/extensibility/plugins/manager.ts +1142 -0
  504. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  505. package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
  506. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  507. package/src/extensibility/plugins/marketplace/manager.ts +927 -0
  508. package/src/extensibility/plugins/marketplace/registry.ts +192 -0
  509. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  510. package/src/extensibility/plugins/marketplace/types.ts +192 -0
  511. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  512. package/src/extensibility/plugins/parser.ts +107 -0
  513. package/src/extensibility/plugins/runtime-config.ts +9 -0
  514. package/src/extensibility/plugins/types.ts +194 -0
  515. package/src/extensibility/session-handler-types.ts +21 -0
  516. package/src/extensibility/shared-events.ts +401 -0
  517. package/src/extensibility/skills.ts +531 -0
  518. package/src/extensibility/slash-commands.ts +131 -0
  519. package/src/extensibility/tool-event-input.ts +80 -0
  520. package/src/extensibility/tool-proxy.ts +28 -0
  521. package/src/extensibility/utils.ts +192 -0
  522. package/src/goals/index.ts +3 -0
  523. package/src/goals/runtime.ts +521 -0
  524. package/src/goals/state.ts +37 -0
  525. package/src/goals/tools/goal-tool.ts +251 -0
  526. package/src/hindsight/backend.ts +354 -0
  527. package/src/hindsight/bank.ts +156 -0
  528. package/src/hindsight/client.ts +680 -0
  529. package/src/hindsight/config.ts +193 -0
  530. package/src/hindsight/content.ts +266 -0
  531. package/src/hindsight/index.ts +8 -0
  532. package/src/hindsight/mental-models.ts +429 -0
  533. package/src/hindsight/seeds.json +32 -0
  534. package/src/hindsight/state.ts +530 -0
  535. package/src/hindsight/transcript.ts +71 -0
  536. package/src/index.ts +66 -0
  537. package/src/internal-urls/agent-protocol.ts +180 -0
  538. package/src/internal-urls/artifact-protocol.ts +151 -0
  539. package/src/internal-urls/docs-index.ts +102 -0
  540. package/src/internal-urls/filesystem-resource.ts +34 -0
  541. package/src/internal-urls/history-protocol.ts +198 -0
  542. package/src/internal-urls/index.ts +28 -0
  543. package/src/internal-urls/issue-pr-protocol.ts +594 -0
  544. package/src/internal-urls/json-query.ts +126 -0
  545. package/src/internal-urls/local-protocol.ts +471 -0
  546. package/src/internal-urls/mcp-protocol.ts +168 -0
  547. package/src/internal-urls/memory-protocol.ts +380 -0
  548. package/src/internal-urls/omp-protocol.ts +94 -0
  549. package/src/internal-urls/parse.ts +103 -0
  550. package/src/internal-urls/registry-helpers.ts +132 -0
  551. package/src/internal-urls/router.ts +153 -0
  552. package/src/internal-urls/rule-protocol.ts +45 -0
  553. package/src/internal-urls/security-protocol.ts +261 -0
  554. package/src/internal-urls/skill-protocol.ts +131 -0
  555. package/src/internal-urls/ssh-protocol.ts +368 -0
  556. package/src/internal-urls/types.ts +196 -0
  557. package/src/internal-urls/vault-protocol.ts +940 -0
  558. package/src/internal-urls/xd-protocol.ts +46 -0
  559. package/src/irc/bus.ts +380 -0
  560. package/src/jsonrpc/message-framing.ts +142 -0
  561. package/src/launch/broker.ts +1368 -0
  562. package/src/launch/client.ts +521 -0
  563. package/src/launch/ensure.ts +73 -0
  564. package/src/launch/paths.ts +14 -0
  565. package/src/launch/presence.ts +82 -0
  566. package/src/launch/protocol.ts +448 -0
  567. package/src/launch/spawn-options.ts +17 -0
  568. package/src/launch/terminal-output-worker-client.ts +53 -0
  569. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  570. package/src/launch/terminal-output-worker.ts +23 -0
  571. package/src/launch/terminal-output.ts +46 -0
  572. package/src/lib/xai-http.ts +150 -0
  573. package/src/live/attestation.ts +91 -0
  574. package/src/live/controller.ts +517 -0
  575. package/src/live/prompts/agent-final-message.md +3 -0
  576. package/src/live/prompts/live-instructions.md +23 -0
  577. package/src/live/protocol.ts +233 -0
  578. package/src/live/transport.ts +422 -0
  579. package/src/live/visualizer.ts +221 -0
  580. package/src/live/voices.ts +18 -0
  581. package/src/lsp/client.ts +1465 -0
  582. package/src/lsp/clients/biome-client.ts +263 -0
  583. package/src/lsp/clients/index.ts +50 -0
  584. package/src/lsp/clients/lsp-linter-client.ts +85 -0
  585. package/src/lsp/clients/swiftlint-client.ts +120 -0
  586. package/src/lsp/config.ts +549 -0
  587. package/src/lsp/defaults.json +499 -0
  588. package/src/lsp/deferred-diagnostics.ts +66 -0
  589. package/src/lsp/diagnostics-ledger.ts +51 -0
  590. package/src/lsp/edits.ts +288 -0
  591. package/src/lsp/format-options.ts +119 -0
  592. package/src/lsp/index.ts +2821 -0
  593. package/src/lsp/lspmux.ts +233 -0
  594. package/src/lsp/mux/daemon.ts +348 -0
  595. package/src/lsp/mux/protocol.ts +96 -0
  596. package/src/lsp/mux/server.ts +797 -0
  597. package/src/lsp/render.ts +668 -0
  598. package/src/lsp/startup-events.ts +13 -0
  599. package/src/lsp/types.ts +479 -0
  600. package/src/lsp/utils.ts +747 -0
  601. package/src/main.ts +1799 -0
  602. package/src/markit/NOTICE +32 -0
  603. package/src/markit/converters/docx.ts +56 -0
  604. package/src/markit/converters/epub.ts +136 -0
  605. package/src/markit/converters/pdf/columns.ts +103 -0
  606. package/src/markit/converters/pdf/extract.ts +598 -0
  607. package/src/markit/converters/pdf/grid.ts +780 -0
  608. package/src/markit/converters/pdf/headers.ts +106 -0
  609. package/src/markit/converters/pdf/index.ts +146 -0
  610. package/src/markit/converters/pdf/render.ts +501 -0
  611. package/src/markit/converters/pdf/types.ts +84 -0
  612. package/src/markit/converters/pptx.ts +325 -0
  613. package/src/markit/converters/xlsx.ts +173 -0
  614. package/src/markit/index.ts +2 -0
  615. package/src/markit/registry.ts +59 -0
  616. package/src/markit/types.ts +35 -0
  617. package/src/mcp/client.ts +511 -0
  618. package/src/mcp/config-writer.ts +377 -0
  619. package/src/mcp/config.ts +385 -0
  620. package/src/mcp/index.ts +29 -0
  621. package/src/mcp/json-rpc.ts +122 -0
  622. package/src/mcp/loader.ts +125 -0
  623. package/src/mcp/manager.ts +1524 -0
  624. package/src/mcp/oauth-credentials.ts +104 -0
  625. package/src/mcp/oauth-discovery.ts +587 -0
  626. package/src/mcp/oauth-flow.ts +830 -0
  627. package/src/mcp/render.ts +214 -0
  628. package/src/mcp/request-id.ts +24 -0
  629. package/src/mcp/smithery-auth.ts +108 -0
  630. package/src/mcp/smithery-connect.ts +154 -0
  631. package/src/mcp/smithery-registry.ts +500 -0
  632. package/src/mcp/startup-events.ts +116 -0
  633. package/src/mcp/timeout.ts +59 -0
  634. package/src/mcp/tool-bridge.ts +691 -0
  635. package/src/mcp/tool-cache.ts +117 -0
  636. package/src/mcp/transports/header-policy.ts +95 -0
  637. package/src/mcp/transports/http.ts +508 -0
  638. package/src/mcp/transports/index.ts +7 -0
  639. package/src/mcp/transports/sse.ts +372 -0
  640. package/src/mcp/transports/stdio.ts +905 -0
  641. package/src/mcp/types.ts +467 -0
  642. package/src/memories/index.ts +1434 -0
  643. package/src/memories/storage.ts +578 -0
  644. package/src/memory-backend/index.ts +19 -0
  645. package/src/memory-backend/local-backend.ts +47 -0
  646. package/src/memory-backend/messages.ts +19 -0
  647. package/src/memory-backend/off-backend.ts +25 -0
  648. package/src/memory-backend/resolve.ts +25 -0
  649. package/src/memory-backend/runtime.ts +66 -0
  650. package/src/memory-backend/tool-names.ts +2 -0
  651. package/src/memory-backend/types.ts +166 -0
  652. package/src/mnemopi/backend.ts +629 -0
  653. package/src/mnemopi/config.ts +267 -0
  654. package/src/mnemopi/embed-client.ts +293 -0
  655. package/src/mnemopi/embed-protocol.ts +35 -0
  656. package/src/mnemopi/embed-worker.ts +114 -0
  657. package/src/mnemopi/index.ts +3 -0
  658. package/src/mnemopi/state.ts +940 -0
  659. package/src/modes/acp/acp-agent.ts +2595 -0
  660. package/src/modes/acp/acp-client-bridge.ts +154 -0
  661. package/src/modes/acp/acp-event-mapper.ts +1084 -0
  662. package/src/modes/acp/acp-mode.ts +48 -0
  663. package/src/modes/acp/index.ts +2 -0
  664. package/src/modes/acp/terminal-auth.ts +37 -0
  665. package/src/modes/components/advisor-config.ts +635 -0
  666. package/src/modes/components/advisor-message.ts +109 -0
  667. package/src/modes/components/agent-dashboard.ts +1252 -0
  668. package/src/modes/components/agent-hub-projection.ts +248 -0
  669. package/src/modes/components/agent-hub-renderer.ts +194 -0
  670. package/src/modes/components/agent-hub.ts +1126 -0
  671. package/src/modes/components/agent-transcript-viewer.ts +647 -0
  672. package/src/modes/components/ask-dialog.ts +1017 -0
  673. package/src/modes/components/assistant-message.ts +978 -0
  674. package/src/modes/components/background-tan-message.ts +36 -0
  675. package/src/modes/components/bash-execution.ts +233 -0
  676. package/src/modes/components/bordered-loader.ts +41 -0
  677. package/src/modes/components/btw-panel.ts +161 -0
  678. package/src/modes/components/cache-invalidation-marker.ts +110 -0
  679. package/src/modes/components/chat-block.ts +111 -0
  680. package/src/modes/components/chat-transcript-builder.ts +518 -0
  681. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  682. package/src/modes/components/collab-prompt-message.ts +32 -0
  683. package/src/modes/components/compaction-summary-message.ts +221 -0
  684. package/src/modes/components/copy-selector.ts +218 -0
  685. package/src/modes/components/countdown-timer.ts +75 -0
  686. package/src/modes/components/custom-editor.ts +1036 -0
  687. package/src/modes/components/custom-message.ts +70 -0
  688. package/src/modes/components/diff.ts +254 -0
  689. package/src/modes/components/dynamic-border.ts +37 -0
  690. package/src/modes/components/error-banner.ts +33 -0
  691. package/src/modes/components/eval-execution.ts +169 -0
  692. package/src/modes/components/execution-shared.ts +101 -0
  693. package/src/modes/components/extensions/extension-dashboard.ts +492 -0
  694. package/src/modes/components/extensions/extension-list.ts +507 -0
  695. package/src/modes/components/extensions/index.ts +9 -0
  696. package/src/modes/components/extensions/inspector-panel.ts +326 -0
  697. package/src/modes/components/extensions/state-manager.ts +648 -0
  698. package/src/modes/components/extensions/types.ts +186 -0
  699. package/src/modes/components/footer.ts +274 -0
  700. package/src/modes/components/history-search.ts +268 -0
  701. package/src/modes/components/hook-editor.ts +213 -0
  702. package/src/modes/components/hook-input.ts +87 -0
  703. package/src/modes/components/hook-message.ts +67 -0
  704. package/src/modes/components/hook-selector.ts +691 -0
  705. package/src/modes/components/index.ts +42 -0
  706. package/src/modes/components/keybinding-hints.ts +56 -0
  707. package/src/modes/components/late-diagnostics-message.ts +60 -0
  708. package/src/modes/components/login-dialog.ts +197 -0
  709. package/src/modes/components/logout-account-selector.ts +130 -0
  710. package/src/modes/components/mcp-add-wizard.ts +1413 -0
  711. package/src/modes/components/message-frame.ts +98 -0
  712. package/src/modes/components/model-browser.ts +888 -0
  713. package/src/modes/components/model-hub.ts +2014 -0
  714. package/src/modes/components/model-picker.ts +237 -0
  715. package/src/modes/components/move-overlay.ts +293 -0
  716. package/src/modes/components/oauth-selector.ts +474 -0
  717. package/src/modes/components/omfg-panel.ts +141 -0
  718. package/src/modes/components/overlay-box.ts +109 -0
  719. package/src/modes/components/pause-screen.ts +208 -0
  720. package/src/modes/components/plan-review-overlay.ts +1226 -0
  721. package/src/modes/components/plan-toc.ts +138 -0
  722. package/src/modes/components/plugin-selector.ts +100 -0
  723. package/src/modes/components/plugin-settings.ts +745 -0
  724. package/src/modes/components/queue-mode-selector.ts +61 -0
  725. package/src/modes/components/read-tool-group.ts +856 -0
  726. package/src/modes/components/reset-usage-selector.ts +161 -0
  727. package/src/modes/components/segment-track.ts +89 -0
  728. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  729. package/src/modes/components/selector-helpers.ts +129 -0
  730. package/src/modes/components/session-account-selector.ts +62 -0
  731. package/src/modes/components/session-selector.ts +1035 -0
  732. package/src/modes/components/settings-defs.ts +267 -0
  733. package/src/modes/components/settings-selector.ts +1445 -0
  734. package/src/modes/components/show-images-selector.ts +50 -0
  735. package/src/modes/components/skill-message.ts +110 -0
  736. package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
  737. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  738. package/src/modes/components/status-line/component.ts +1843 -0
  739. package/src/modes/components/status-line/context-thresholds.ts +86 -0
  740. package/src/modes/components/status-line/git-utils.ts +42 -0
  741. package/src/modes/components/status-line/index.ts +5 -0
  742. package/src/modes/components/status-line/presets.ts +106 -0
  743. package/src/modes/components/status-line/segments.ts +710 -0
  744. package/src/modes/components/status-line/separators.ts +55 -0
  745. package/src/modes/components/status-line/types.ts +159 -0
  746. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  747. package/src/modes/components/theme-selector.ts +68 -0
  748. package/src/modes/components/thinking-selector.ts +57 -0
  749. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  750. package/src/modes/components/tips.txt +26 -0
  751. package/src/modes/components/todo-reminder.ts +43 -0
  752. package/src/modes/components/tool-execution.ts +1467 -0
  753. package/src/modes/components/transcript-container.ts +527 -0
  754. package/src/modes/components/tree-selector.ts +1006 -0
  755. package/src/modes/components/ttsr-notification.ts +123 -0
  756. package/src/modes/components/usage-row.ts +52 -0
  757. package/src/modes/components/user-message-selector.ts +227 -0
  758. package/src/modes/components/user-message.ts +167 -0
  759. package/src/modes/components/visual-truncate.ts +63 -0
  760. package/src/modes/components/welcome.ts +578 -0
  761. package/src/modes/controllers/btw-controller.ts +246 -0
  762. package/src/modes/controllers/command-controller-shared.ts +109 -0
  763. package/src/modes/controllers/command-controller.ts +2024 -0
  764. package/src/modes/controllers/event-controller.ts +2173 -0
  765. package/src/modes/controllers/extension-ui-controller.ts +1243 -0
  766. package/src/modes/controllers/input-controller.ts +2037 -0
  767. package/src/modes/controllers/live-command-controller.ts +261 -0
  768. package/src/modes/controllers/mcp-command-controller.ts +2552 -0
  769. package/src/modes/controllers/omfg-controller.ts +287 -0
  770. package/src/modes/controllers/omfg-rule.ts +647 -0
  771. package/src/modes/controllers/selector-controller.ts +2058 -0
  772. package/src/modes/controllers/session-focus-controller.ts +117 -0
  773. package/src/modes/controllers/ssh-command-controller.ts +385 -0
  774. package/src/modes/controllers/streaming-reveal.ts +399 -0
  775. package/src/modes/controllers/tan-command-controller.ts +243 -0
  776. package/src/modes/controllers/todo-command-controller.ts +487 -0
  777. package/src/modes/controllers/tool-args-reveal.ts +591 -0
  778. package/src/modes/data/emojis.json +1 -0
  779. package/src/modes/emoji-autocomplete.ts +285 -0
  780. package/src/modes/github-ref-autocomplete.ts +75 -0
  781. package/src/modes/gradient-highlight.ts +99 -0
  782. package/src/modes/image-references.ts +137 -0
  783. package/src/modes/index.ts +10 -0
  784. package/src/modes/interactive-mode.ts +5055 -0
  785. package/src/modes/internal-url-autocomplete.ts +158 -0
  786. package/src/modes/loop-limit.ts +192 -0
  787. package/src/modes/magic-keyword-boundary.ts +23 -0
  788. package/src/modes/magic-keywords.ts +42 -0
  789. package/src/modes/markdown-prose.ts +247 -0
  790. package/src/modes/oauth-manual-input.ts +69 -0
  791. package/src/modes/orchestrate.ts +43 -0
  792. package/src/modes/print-mode.ts +282 -0
  793. package/src/modes/prompt-action-autocomplete.ts +322 -0
  794. package/src/modes/queue-input.ts +132 -0
  795. package/src/modes/rpc/host-tools.ts +204 -0
  796. package/src/modes/rpc/host-uris.ts +241 -0
  797. package/src/modes/rpc/rpc-client.ts +1213 -0
  798. package/src/modes/rpc/rpc-frame.ts +316 -0
  799. package/src/modes/rpc/rpc-input.ts +38 -0
  800. package/src/modes/rpc/rpc-messages.ts +127 -0
  801. package/src/modes/rpc/rpc-mode.ts +1519 -0
  802. package/src/modes/rpc/rpc-subagents.ts +265 -0
  803. package/src/modes/rpc/rpc-types.ts +544 -0
  804. package/src/modes/running-subagent-badge.ts +13 -0
  805. package/src/modes/runtime-init.ts +144 -0
  806. package/src/modes/session-observer-registry.ts +223 -0
  807. package/src/modes/session-teardown.ts +82 -0
  808. package/src/modes/setup-version.ts +11 -0
  809. package/src/modes/setup-wizard/index.ts +103 -0
  810. package/src/modes/setup-wizard/lazy.ts +16 -0
  811. package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
  812. package/src/modes/setup-wizard/scenes/model.ts +132 -0
  813. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  814. package/src/modes/setup-wizard/scenes/providers.ts +105 -0
  815. package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
  816. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  817. package/src/modes/setup-wizard/scenes/theme.ts +330 -0
  818. package/src/modes/setup-wizard/scenes/types.ts +65 -0
  819. package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
  820. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  821. package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
  822. package/src/modes/shared.ts +49 -0
  823. package/src/modes/skill-command.ts +91 -0
  824. package/src/modes/theme/dark.json +95 -0
  825. package/src/modes/theme/defaults/alabaster.json +93 -0
  826. package/src/modes/theme/defaults/amethyst.json +96 -0
  827. package/src/modes/theme/defaults/anthracite.json +93 -0
  828. package/src/modes/theme/defaults/basalt.json +91 -0
  829. package/src/modes/theme/defaults/birch.json +95 -0
  830. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  831. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  832. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  833. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  834. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  835. package/src/modes/theme/defaults/dark-copper.json +95 -0
  836. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  837. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  838. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  839. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  840. package/src/modes/theme/defaults/dark-ember.json +95 -0
  841. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  842. package/src/modes/theme/defaults/dark-forest.json +96 -0
  843. package/src/modes/theme/defaults/dark-github.json +105 -0
  844. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  845. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  846. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  847. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  848. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  849. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  850. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  851. package/src/modes/theme/defaults/dark-nord.json +97 -0
  852. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  853. package/src/modes/theme/defaults/dark-one.json +100 -0
  854. package/src/modes/theme/defaults/dark-poimandres.json +143 -0
  855. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  856. package/src/modes/theme/defaults/dark-reef.json +91 -0
  857. package/src/modes/theme/defaults/dark-retro.json +92 -0
  858. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  859. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  860. package/src/modes/theme/defaults/dark-slate.json +95 -0
  861. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  862. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  863. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  864. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  865. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  866. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  867. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  868. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  869. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  870. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  871. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  872. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  873. package/src/modes/theme/defaults/graphite.json +92 -0
  874. package/src/modes/theme/defaults/index.ts +199 -0
  875. package/src/modes/theme/defaults/light-arctic.json +107 -0
  876. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  877. package/src/modes/theme/defaults/light-canyon.json +91 -0
  878. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  879. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  880. package/src/modes/theme/defaults/light-coral.json +95 -0
  881. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  882. package/src/modes/theme/defaults/light-dawn.json +90 -0
  883. package/src/modes/theme/defaults/light-dunes.json +91 -0
  884. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  885. package/src/modes/theme/defaults/light-forest.json +100 -0
  886. package/src/modes/theme/defaults/light-frost.json +95 -0
  887. package/src/modes/theme/defaults/light-github.json +115 -0
  888. package/src/modes/theme/defaults/light-glacier.json +91 -0
  889. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  890. package/src/modes/theme/defaults/light-haze.json +90 -0
  891. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  892. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  893. package/src/modes/theme/defaults/light-lavender.json +95 -0
  894. package/src/modes/theme/defaults/light-meadow.json +91 -0
  895. package/src/modes/theme/defaults/light-mint.json +95 -0
  896. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  897. package/src/modes/theme/defaults/light-ocean.json +99 -0
  898. package/src/modes/theme/defaults/light-one.json +99 -0
  899. package/src/modes/theme/defaults/light-opal.json +91 -0
  900. package/src/modes/theme/defaults/light-orchard.json +91 -0
  901. package/src/modes/theme/defaults/light-paper.json +95 -0
  902. package/src/modes/theme/defaults/light-poimandres.json +143 -0
  903. package/src/modes/theme/defaults/light-prism.json +90 -0
  904. package/src/modes/theme/defaults/light-retro.json +98 -0
  905. package/src/modes/theme/defaults/light-sand.json +95 -0
  906. package/src/modes/theme/defaults/light-savanna.json +91 -0
  907. package/src/modes/theme/defaults/light-solarized.json +102 -0
  908. package/src/modes/theme/defaults/light-soleil.json +90 -0
  909. package/src/modes/theme/defaults/light-sunset.json +99 -0
  910. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  911. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  912. package/src/modes/theme/defaults/light-wetland.json +91 -0
  913. package/src/modes/theme/defaults/light-zenith.json +89 -0
  914. package/src/modes/theme/defaults/limestone.json +94 -0
  915. package/src/modes/theme/defaults/mahogany.json +97 -0
  916. package/src/modes/theme/defaults/marble.json +93 -0
  917. package/src/modes/theme/defaults/obsidian.json +91 -0
  918. package/src/modes/theme/defaults/onyx.json +91 -0
  919. package/src/modes/theme/defaults/pearl.json +93 -0
  920. package/src/modes/theme/defaults/porcelain.json +91 -0
  921. package/src/modes/theme/defaults/quartz.json +96 -0
  922. package/src/modes/theme/defaults/sandstone.json +95 -0
  923. package/src/modes/theme/defaults/titanium.json +90 -0
  924. package/src/modes/theme/light.json +93 -0
  925. package/src/modes/theme/mermaid-cache.ts +92 -0
  926. package/src/modes/theme/shimmer.ts +305 -0
  927. package/src/modes/theme/theme-schema.json +463 -0
  928. package/src/modes/theme/theme.ts +3171 -0
  929. package/src/modes/turn-budget.ts +31 -0
  930. package/src/modes/types.ts +494 -0
  931. package/src/modes/ultrathink.ts +42 -0
  932. package/src/modes/utils/context-usage.ts +518 -0
  933. package/src/modes/utils/copy-targets.ts +378 -0
  934. package/src/modes/utils/hotkeys-markdown.ts +65 -0
  935. package/src/modes/utils/interactive-context-helpers.ts +31 -0
  936. package/src/modes/utils/keybinding-matchers.ts +86 -0
  937. package/src/modes/utils/tools-markdown.ts +31 -0
  938. package/src/modes/utils/transcript-render-helpers.ts +253 -0
  939. package/src/modes/utils/ui-helpers.ts +976 -0
  940. package/src/modes/warp-events.ts +232 -0
  941. package/src/modes/workflow.ts +55 -0
  942. package/src/plan-mode/approved-plan.ts +194 -0
  943. package/src/plan-mode/model-transition.ts +51 -0
  944. package/src/plan-mode/plan-files.ts +40 -0
  945. package/src/plan-mode/plan-handoff.ts +37 -0
  946. package/src/plan-mode/plan-protection.ts +31 -0
  947. package/src/plan-mode/state.ts +6 -0
  948. package/src/priority.json +60 -0
  949. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  950. package/src/prompts/advisor/advise-tool.md +3 -0
  951. package/src/prompts/advisor/context-files.md +8 -0
  952. package/src/prompts/advisor/system.md +98 -0
  953. package/src/prompts/agents/designer.md +74 -0
  954. package/src/prompts/agents/frontmatter.md +12 -0
  955. package/src/prompts/agents/init.md +33 -0
  956. package/src/prompts/agents/librarian.md +119 -0
  957. package/src/prompts/agents/reviewer.md +139 -0
  958. package/src/prompts/agents/scout.md +58 -0
  959. package/src/prompts/agents/security-reviewer.md +75 -0
  960. package/src/prompts/agents/task.md +17 -0
  961. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  962. package/src/prompts/bench/cache-prefix.md +3 -0
  963. package/src/prompts/bench/cache-suffix.md +1 -0
  964. package/src/prompts/bench.md +6 -0
  965. package/src/prompts/ci-green-request.md +36 -0
  966. package/src/prompts/dry-balance-bench.md +8 -0
  967. package/src/prompts/goals/goal-budget-limit.md +16 -0
  968. package/src/prompts/goals/goal-continuation.md +28 -0
  969. package/src/prompts/goals/goal-mode-active.md +23 -0
  970. package/src/prompts/goals/goal-mode-context.md +4 -0
  971. package/src/prompts/goals/goal-todo-context.md +12 -0
  972. package/src/prompts/goals/guided-goal-interview.md +43 -0
  973. package/src/prompts/memories/consolidation.md +30 -0
  974. package/src/prompts/memories/consolidation_system.md +4 -0
  975. package/src/prompts/memories/read-path.md +17 -0
  976. package/src/prompts/memories/stage_one_input.md +6 -0
  977. package/src/prompts/memories/stage_one_system.md +21 -0
  978. package/src/prompts/review-custom-request.md +21 -0
  979. package/src/prompts/review-headless-request.md +16 -0
  980. package/src/prompts/review-request.md +68 -0
  981. package/src/prompts/security/scan-coordinator.md +7 -0
  982. package/src/prompts/security/scan-request.md +21 -0
  983. package/src/prompts/security/validate-request.md +8 -0
  984. package/src/prompts/session/launch-completion.md +1 -0
  985. package/src/prompts/skills/autoload.md +8 -0
  986. package/src/prompts/skills/user-invocation.md +11 -0
  987. package/src/prompts/steering/parent-irc.md +5 -0
  988. package/src/prompts/steering/user-interjection.md +6 -0
  989. package/src/prompts/system/active-repo-context.md +4 -0
  990. package/src/prompts/system/agent-creation-architect.md +50 -0
  991. package/src/prompts/system/agent-creation-user.md +6 -0
  992. package/src/prompts/system/auto-continue.md +1 -0
  993. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  994. package/src/prompts/system/auto-thinking-difficulty.md +14 -0
  995. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  996. package/src/prompts/system/autolearn-guidance.md +7 -0
  997. package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
  998. package/src/prompts/system/background-tan-dispatch.md +8 -0
  999. package/src/prompts/system/btw-user.md +8 -0
  1000. package/src/prompts/system/commit-message-system.md +14 -0
  1001. package/src/prompts/system/computer-safety.md +14 -0
  1002. package/src/prompts/system/custom-system-prompt.md +64 -0
  1003. package/src/prompts/system/eager-task.md +7 -0
  1004. package/src/prompts/system/eager-todo.md +18 -0
  1005. package/src/prompts/system/empty-stop-retry.md +4 -0
  1006. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  1007. package/src/prompts/system/interrupted-thinking.md +7 -0
  1008. package/src/prompts/system/irc-autoreply.md +6 -0
  1009. package/src/prompts/system/irc-incoming.md +9 -0
  1010. package/src/prompts/system/manual-continue.md +7 -0
  1011. package/src/prompts/system/mcp-xdev-guidance.md +11 -0
  1012. package/src/prompts/system/memory-consolidation-system.md +8 -0
  1013. package/src/prompts/system/memory-extraction-system.md +26 -0
  1014. package/src/prompts/system/mid-run-todo-nudge.md +3 -0
  1015. package/src/prompts/system/omfg-user.md +50 -0
  1016. package/src/prompts/system/orchestrate-notice.md +40 -0
  1017. package/src/prompts/system/personalities/default.md +18 -0
  1018. package/src/prompts/system/personalities/friendly.md +17 -0
  1019. package/src/prompts/system/personalities/pragmatic.md +15 -0
  1020. package/src/prompts/system/plan-mode-active.md +125 -0
  1021. package/src/prompts/system/plan-mode-approved.md +22 -0
  1022. package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
  1023. package/src/prompts/system/plan-mode-reference.md +10 -0
  1024. package/src/prompts/system/plan-mode-subagent.md +33 -0
  1025. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  1026. package/src/prompts/system/plan-yolo-handoff.md +5 -0
  1027. package/src/prompts/system/prewalk-checklist.md +7 -0
  1028. package/src/prompts/system/prewalk-continue.md +1 -0
  1029. package/src/prompts/system/prewalk-plan.md +13 -0
  1030. package/src/prompts/system/project-prompt.md +61 -0
  1031. package/src/prompts/system/recap-user.md +9 -0
  1032. package/src/prompts/system/resolve-device-reminder.md +3 -0
  1033. package/src/prompts/system/rewind-report.md +6 -0
  1034. package/src/prompts/system/side-channel-no-tools.md +3 -0
  1035. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  1036. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  1037. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  1038. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  1039. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  1040. package/src/prompts/system/speech-rewrite.md +15 -0
  1041. package/src/prompts/system/subagent-async-pending.md +6 -0
  1042. package/src/prompts/system/subagent-system-prompt.md +73 -0
  1043. package/src/prompts/system/subagent-user-prompt.md +3 -0
  1044. package/src/prompts/system/subagent-yield-reminder.md +23 -0
  1045. package/src/prompts/system/system-prompt.md +263 -0
  1046. package/src/prompts/system/tan-context-switch.md +17 -0
  1047. package/src/prompts/system/task-label.md +23 -0
  1048. package/src/prompts/system/thinking-loop-redirect.md +10 -0
  1049. package/src/prompts/system/title-marker-instruction.md +1 -0
  1050. package/src/prompts/system/title-system.md +16 -0
  1051. package/src/prompts/system/tool-call-loop-redirect.md +8 -0
  1052. package/src/prompts/system/ttsr-interrupt.md +7 -0
  1053. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  1054. package/src/prompts/system/ultrathink-notice.md +3 -0
  1055. package/src/prompts/system/unexpected-stop-classifier.md +17 -0
  1056. package/src/prompts/system/unexpected-stop-retry.md +4 -0
  1057. package/src/prompts/system/vibe-mode-active.md +26 -0
  1058. package/src/prompts/system/web-search.md +25 -0
  1059. package/src/prompts/system/workflow-notice.md +112 -0
  1060. package/src/prompts/system/xdev-mount-notice.md +20 -0
  1061. package/src/prompts/tools/apply-patch.md +65 -0
  1062. package/src/prompts/tools/ask.md +22 -0
  1063. package/src/prompts/tools/ast-edit.md +11 -0
  1064. package/src/prompts/tools/ast-grep.md +19 -0
  1065. package/src/prompts/tools/async-result.md +8 -0
  1066. package/src/prompts/tools/bash.md +23 -0
  1067. package/src/prompts/tools/browser.md +29 -0
  1068. package/src/prompts/tools/checkpoint.md +15 -0
  1069. package/src/prompts/tools/computer.md +26 -0
  1070. package/src/prompts/tools/debug.md +3 -0
  1071. package/src/prompts/tools/eval.md +45 -0
  1072. package/src/prompts/tools/github.md +22 -0
  1073. package/src/prompts/tools/glob.md +16 -0
  1074. package/src/prompts/tools/goal.md +11 -0
  1075. package/src/prompts/tools/grep.md +13 -0
  1076. package/src/prompts/tools/hub.md +34 -0
  1077. package/src/prompts/tools/image-attachment-describe-system.md +8 -0
  1078. package/src/prompts/tools/image-attachment-describe.md +10 -0
  1079. package/src/prompts/tools/image-gen.md +7 -0
  1080. package/src/prompts/tools/inspect-image-system.md +20 -0
  1081. package/src/prompts/tools/inspect-image.md +22 -0
  1082. package/src/prompts/tools/learn.md +7 -0
  1083. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  1084. package/src/prompts/tools/lsp.md +19 -0
  1085. package/src/prompts/tools/manage-skill.md +9 -0
  1086. package/src/prompts/tools/memory-edit.md +12 -0
  1087. package/src/prompts/tools/patch.md +57 -0
  1088. package/src/prompts/tools/read.md +27 -0
  1089. package/src/prompts/tools/recall.md +7 -0
  1090. package/src/prompts/tools/reflect.md +5 -0
  1091. package/src/prompts/tools/replace.md +30 -0
  1092. package/src/prompts/tools/retain.md +6 -0
  1093. package/src/prompts/tools/rewind.md +14 -0
  1094. package/src/prompts/tools/security-publish.md +1 -0
  1095. package/src/prompts/tools/security-scan.md +1 -0
  1096. package/src/prompts/tools/task-async-contract.md +1 -0
  1097. package/src/prompts/tools/task-summary.md +20 -0
  1098. package/src/prompts/tools/task.md +83 -0
  1099. package/src/prompts/tools/todo.md +42 -0
  1100. package/src/prompts/tools/vibe-kill.md +3 -0
  1101. package/src/prompts/tools/vibe-list.md +3 -0
  1102. package/src/prompts/tools/vibe-send.md +9 -0
  1103. package/src/prompts/tools/vibe-spawn.md +10 -0
  1104. package/src/prompts/tools/vibe-turn-result.md +19 -0
  1105. package/src/prompts/tools/vibe-wait.md +8 -0
  1106. package/src/prompts/tools/web-search.md +8 -0
  1107. package/src/prompts/tools/write.md +14 -0
  1108. package/src/registry/agent-lifecycle.ts +505 -0
  1109. package/src/registry/agent-registry.ts +287 -0
  1110. package/src/registry/persisted-agents.ts +423 -0
  1111. package/src/sdk.ts +3820 -0
  1112. package/src/secrets/index.ts +375 -0
  1113. package/src/secrets/obfuscator.ts +2629 -0
  1114. package/src/secrets/regex.ts +21 -0
  1115. package/src/security/auth.ts +98 -0
  1116. package/src/security/cloud.ts +686 -0
  1117. package/src/security/comparison.ts +255 -0
  1118. package/src/security/contracts/ids.ts +111 -0
  1119. package/src/security/contracts/index.ts +4 -0
  1120. package/src/security/contracts/schemas.ts +219 -0
  1121. package/src/security/contracts/types.ts +254 -0
  1122. package/src/security/contracts/validation.ts +69 -0
  1123. package/src/security/coordinator.ts +708 -0
  1124. package/src/security/importers/codex-security.ts +387 -0
  1125. package/src/security/importers/index.ts +2 -0
  1126. package/src/security/importers/sarif.ts +357 -0
  1127. package/src/security/index.ts +13 -0
  1128. package/src/security/preflight.ts +405 -0
  1129. package/src/security/provenance.ts +106 -0
  1130. package/src/security/publication.ts +326 -0
  1131. package/src/security/remediation.ts +93 -0
  1132. package/src/security/resource-output.ts +50 -0
  1133. package/src/security/sarif.ts +78 -0
  1134. package/src/security/store.ts +430 -0
  1135. package/src/session/acp-permission-gate.ts +165 -0
  1136. package/src/session/agent-session-events.ts +67 -0
  1137. package/src/session/agent-session-types.ts +407 -0
  1138. package/src/session/agent-session.ts +9000 -0
  1139. package/src/session/agent-storage.ts +807 -0
  1140. package/src/session/artifacts.ts +154 -0
  1141. package/src/session/async-job-delivery.ts +82 -0
  1142. package/src/session/auth-broker-config.ts +92 -0
  1143. package/src/session/auth-storage.ts +25 -0
  1144. package/src/session/bash-runner.ts +326 -0
  1145. package/src/session/blob-store.ts +295 -0
  1146. package/src/session/checkpoint-entries.ts +81 -0
  1147. package/src/session/claude-session-store.ts +426 -0
  1148. package/src/session/client-bridge.ts +85 -0
  1149. package/src/session/codex-auto-reset.ts +673 -0
  1150. package/src/session/codex-session-store.ts +673 -0
  1151. package/src/session/compact-modes.ts +105 -0
  1152. package/src/session/credential-pin.ts +93 -0
  1153. package/src/session/eval-runner.ts +219 -0
  1154. package/src/session/exit-diagnostics.ts +310 -0
  1155. package/src/session/foreign-session-import.ts +52 -0
  1156. package/src/session/foreign-session-jsonl.ts +29 -0
  1157. package/src/session/foreign-session-store.ts +26 -0
  1158. package/src/session/history-storage.ts +329 -0
  1159. package/src/session/indexed-session-storage.ts +553 -0
  1160. package/src/session/irc-bridge.ts +203 -0
  1161. package/src/session/launch-completion.ts +37 -0
  1162. package/src/session/messages.ts +1304 -0
  1163. package/src/session/model-controls.ts +757 -0
  1164. package/src/session/prewalk.ts +279 -0
  1165. package/src/session/provider-image-budget.ts +86 -0
  1166. package/src/session/queued-messages.ts +99 -0
  1167. package/src/session/redis-session-storage.ts +257 -0
  1168. package/src/session/retry-fallback-chains.ts +455 -0
  1169. package/src/session/role-models.ts +85 -0
  1170. package/src/session/session-advisors.ts +1896 -0
  1171. package/src/session/session-context.ts +584 -0
  1172. package/src/session/session-dump-format.ts +216 -0
  1173. package/src/session/session-entries.ts +306 -0
  1174. package/src/session/session-handoff.ts +325 -0
  1175. package/src/session/session-history-format.ts +459 -0
  1176. package/src/session/session-listing.ts +715 -0
  1177. package/src/session/session-loader.ts +362 -0
  1178. package/src/session/session-maintenance.ts +3115 -0
  1179. package/src/session/session-manager.ts +2747 -0
  1180. package/src/session/session-memory.ts +222 -0
  1181. package/src/session/session-metadata.ts +53 -0
  1182. package/src/session/session-migrations.ts +78 -0
  1183. package/src/session/session-paths.ts +280 -0
  1184. package/src/session/session-persistence.ts +293 -0
  1185. package/src/session/session-provider-boundary.ts +306 -0
  1186. package/src/session/session-stats.ts +349 -0
  1187. package/src/session/session-storage.ts +774 -0
  1188. package/src/session/session-title-slot.ts +141 -0
  1189. package/src/session/session-tools.ts +1385 -0
  1190. package/src/session/session-workspace.ts +53 -0
  1191. package/src/session/settings-stream-fn.ts +79 -0
  1192. package/src/session/shake-types.ts +43 -0
  1193. package/src/session/snapcompact-inline.ts +545 -0
  1194. package/src/session/snapcompact-savings-journal.ts +113 -0
  1195. package/src/session/sql-session-storage.ts +374 -0
  1196. package/src/session/stream-guards.ts +417 -0
  1197. package/src/session/streaming-output.ts +1459 -0
  1198. package/src/session/todo-tracker.ts +380 -0
  1199. package/src/session/tool-choice-queue.ts +305 -0
  1200. package/src/session/ttsr-coordinator.ts +496 -0
  1201. package/src/session/turn-persistence.ts +142 -0
  1202. package/src/session/turn-recovery.ts +2005 -0
  1203. package/src/session/unexpected-stop-classifier.ts +141 -0
  1204. package/src/session/yield-queue.ts +288 -0
  1205. package/src/slash-commands/acp-builtins.ts +70 -0
  1206. package/src/slash-commands/available-commands.ts +105 -0
  1207. package/src/slash-commands/builtin-registry.ts +3149 -0
  1208. package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
  1209. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  1210. package/src/slash-commands/helpers/context-report.ts +66 -0
  1211. package/src/slash-commands/helpers/format.ts +46 -0
  1212. package/src/slash-commands/helpers/logout.ts +108 -0
  1213. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  1214. package/src/slash-commands/helpers/mcp.ts +533 -0
  1215. package/src/slash-commands/helpers/parse.ts +85 -0
  1216. package/src/slash-commands/helpers/reset-usage.ts +68 -0
  1217. package/src/slash-commands/helpers/security.ts +451 -0
  1218. package/src/slash-commands/helpers/session-pin.ts +44 -0
  1219. package/src/slash-commands/helpers/ssh.ts +196 -0
  1220. package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
  1221. package/src/slash-commands/helpers/todo.ts +285 -0
  1222. package/src/slash-commands/helpers/usage-report.ts +198 -0
  1223. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  1224. package/src/slash-commands/types.ts +139 -0
  1225. package/src/ssh/config-writer.ts +183 -0
  1226. package/src/ssh/connection-manager.ts +667 -0
  1227. package/src/ssh/file-transfer.ts +214 -0
  1228. package/src/ssh/sshfs-mount.ts +163 -0
  1229. package/src/ssh/utils.ts +51 -0
  1230. package/src/startup-splash.ts +19 -0
  1231. package/src/stt/asr-client.ts +401 -0
  1232. package/src/stt/asr-protocol.ts +65 -0
  1233. package/src/stt/asr-worker.ts +603 -0
  1234. package/src/stt/downloader.ts +142 -0
  1235. package/src/stt/endpointer.ts +259 -0
  1236. package/src/stt/index.ts +6 -0
  1237. package/src/stt/models.ts +150 -0
  1238. package/src/stt/sherpa-runtime.ts +71 -0
  1239. package/src/stt/stt-controller.ts +320 -0
  1240. package/src/stt/submit-trigger.ts +74 -0
  1241. package/src/subprocess/worker-client.ts +463 -0
  1242. package/src/subprocess/worker-runtime.ts +494 -0
  1243. package/src/system-prompt.ts +921 -0
  1244. package/src/task/agents.ts +170 -0
  1245. package/src/task/commands.ts +132 -0
  1246. package/src/task/discovery.ts +145 -0
  1247. package/src/task/executor.ts +3431 -0
  1248. package/src/task/index.ts +1515 -0
  1249. package/src/task/isolation-ownership.ts +106 -0
  1250. package/src/task/isolation-runner.ts +444 -0
  1251. package/src/task/label.ts +40 -0
  1252. package/src/task/name-generator.ts +1577 -0
  1253. package/src/task/omp-command.ts +26 -0
  1254. package/src/task/output-manager.ts +115 -0
  1255. package/src/task/parallel.ts +221 -0
  1256. package/src/task/persisted-revive.ts +173 -0
  1257. package/src/task/prewalk.ts +6 -0
  1258. package/src/task/prompt-policy.ts +8 -0
  1259. package/src/task/provider-concurrency.ts +100 -0
  1260. package/src/task/read-only-policy.ts +27 -0
  1261. package/src/task/render.ts +1830 -0
  1262. package/src/task/renderer.ts +14 -0
  1263. package/src/task/repair-args.ts +118 -0
  1264. package/src/task/spawn-policy.ts +72 -0
  1265. package/src/task/structured-subagent.ts +676 -0
  1266. package/src/task/subprocess-tool-registry.ts +88 -0
  1267. package/src/task/types.ts +555 -0
  1268. package/src/task/worktree.ts +967 -0
  1269. package/src/task/yield-assembly.ts +198 -0
  1270. package/src/telemetry-export.ts +504 -0
  1271. package/src/thinking.ts +377 -0
  1272. package/src/tiny/device.ts +111 -0
  1273. package/src/tiny/dtype.ts +101 -0
  1274. package/src/tiny/message-preproc.ts +155 -0
  1275. package/src/tiny/models.ts +268 -0
  1276. package/src/tiny/text.ts +290 -0
  1277. package/src/tiny/title-client.ts +460 -0
  1278. package/src/tiny/title-protocol.ts +56 -0
  1279. package/src/tiny/worker.ts +354 -0
  1280. package/src/tools/acp-bridge.ts +125 -0
  1281. package/src/tools/approval.ts +245 -0
  1282. package/src/tools/ask.ts +1459 -0
  1283. package/src/tools/ast-edit.ts +718 -0
  1284. package/src/tools/ast-grep.ts +526 -0
  1285. package/src/tools/auto-generated-guard.ts +335 -0
  1286. package/src/tools/bash-interactive.ts +435 -0
  1287. package/src/tools/bash-interceptor.ts +148 -0
  1288. package/src/tools/bash-pty-selection.ts +14 -0
  1289. package/src/tools/bash-skill-urls.ts +350 -0
  1290. package/src/tools/bash.ts +1826 -0
  1291. package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
  1292. package/src/tools/browser/aria/aria-snapshot.ts +131 -0
  1293. package/src/tools/browser/attach.ts +219 -0
  1294. package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
  1295. package/src/tools/browser/cmux/rpc.ts +200 -0
  1296. package/src/tools/browser/cmux/socket-client.ts +445 -0
  1297. package/src/tools/browser/launch.ts +939 -0
  1298. package/src/tools/browser/readable.ts +112 -0
  1299. package/src/tools/browser/registry.ts +447 -0
  1300. package/src/tools/browser/relay/bridge.ts +945 -0
  1301. package/src/tools/browser/relay/daemon.ts +117 -0
  1302. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  1303. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  1304. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  1305. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  1306. package/src/tools/browser/relay/kind.ts +41 -0
  1307. package/src/tools/browser/relay/protocol.ts +54 -0
  1308. package/src/tools/browser/relay/server.ts +141 -0
  1309. package/src/tools/browser/render.ts +229 -0
  1310. package/src/tools/browser/run-output.ts +76 -0
  1311. package/src/tools/browser/shared-daemon.ts +139 -0
  1312. package/src/tools/browser/tab-protocol.ts +123 -0
  1313. package/src/tools/browser/tab-supervisor.ts +1077 -0
  1314. package/src/tools/browser/tab-worker-entry.ts +29 -0
  1315. package/src/tools/browser/tab-worker.ts +1982 -0
  1316. package/src/tools/browser.ts +487 -0
  1317. package/src/tools/builtin-names.ts +67 -0
  1318. package/src/tools/checkpoint.ts +137 -0
  1319. package/src/tools/computer/exposure.ts +14 -0
  1320. package/src/tools/computer/protocol.ts +69 -0
  1321. package/src/tools/computer/supervisor.ts +409 -0
  1322. package/src/tools/computer/worker-entry.ts +39 -0
  1323. package/src/tools/computer/worker.ts +745 -0
  1324. package/src/tools/computer-renderer.ts +147 -0
  1325. package/src/tools/computer.ts +222 -0
  1326. package/src/tools/conflict-detect.ts +815 -0
  1327. package/src/tools/context.ts +49 -0
  1328. package/src/tools/debug.ts +1121 -0
  1329. package/src/tools/default-renderer.ts +154 -0
  1330. package/src/tools/essential-tools.ts +46 -0
  1331. package/src/tools/eval-backends.ts +34 -0
  1332. package/src/tools/eval-format/index.ts +24 -0
  1333. package/src/tools/eval-format/javascript.ts +952 -0
  1334. package/src/tools/eval-format/julia.ts +446 -0
  1335. package/src/tools/eval-format/python.ts +544 -0
  1336. package/src/tools/eval-format/ruby.ts +380 -0
  1337. package/src/tools/eval-render.ts +784 -0
  1338. package/src/tools/eval.ts +774 -0
  1339. package/src/tools/fetch.ts +1889 -0
  1340. package/src/tools/file-recorder.ts +35 -0
  1341. package/src/tools/fs-cache-invalidation.ts +28 -0
  1342. package/src/tools/gh-cache-invalidation.ts +175 -0
  1343. package/src/tools/gh-format.ts +12 -0
  1344. package/src/tools/gh-renderer.ts +484 -0
  1345. package/src/tools/gh.ts +3958 -0
  1346. package/src/tools/github-cache.ts +663 -0
  1347. package/src/tools/glob.ts +691 -0
  1348. package/src/tools/grep.ts +1920 -0
  1349. package/src/tools/grouped-file-output.ts +210 -0
  1350. package/src/tools/hub/index.ts +579 -0
  1351. package/src/tools/hub/jobs.ts +714 -0
  1352. package/src/tools/hub/launch.ts +690 -0
  1353. package/src/tools/hub/messaging.ts +735 -0
  1354. package/src/tools/hub/types.ts +117 -0
  1355. package/src/tools/image-gen.ts +1689 -0
  1356. package/src/tools/image-providers.ts +50 -0
  1357. package/src/tools/index.ts +736 -0
  1358. package/src/tools/inspect-image-renderer.ts +133 -0
  1359. package/src/tools/inspect-image.ts +316 -0
  1360. package/src/tools/json-tree.ts +260 -0
  1361. package/src/tools/jtd-to-json-schema.ts +219 -0
  1362. package/src/tools/jtd-to-typescript.ts +136 -0
  1363. package/src/tools/jtd-utils.ts +102 -0
  1364. package/src/tools/learn.ts +141 -0
  1365. package/src/tools/list-limit.ts +40 -0
  1366. package/src/tools/manage-skill.ts +102 -0
  1367. package/src/tools/match-line-format.ts +20 -0
  1368. package/src/tools/memory-edit.ts +61 -0
  1369. package/src/tools/memory-recall.ts +102 -0
  1370. package/src/tools/memory-reflect.ts +88 -0
  1371. package/src/tools/memory-render.ts +211 -0
  1372. package/src/tools/memory-retain.ts +89 -0
  1373. package/src/tools/output-meta.ts +860 -0
  1374. package/src/tools/output-schema-validator.ts +307 -0
  1375. package/src/tools/path-utils.ts +1489 -0
  1376. package/src/tools/plan-mode-guard.ts +155 -0
  1377. package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
  1378. package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
  1379. package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
  1380. package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
  1381. package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
  1382. package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
  1383. package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
  1384. package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
  1385. package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
  1386. package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
  1387. package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
  1388. package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
  1389. package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
  1390. package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
  1391. package/src/tools/read.ts +3762 -0
  1392. package/src/tools/render-utils.ts +926 -0
  1393. package/src/tools/renderers.ts +133 -0
  1394. package/src/tools/report-tool-issue.ts +568 -0
  1395. package/src/tools/resolve.ts +423 -0
  1396. package/src/tools/review.ts +103 -0
  1397. package/src/tools/run-scope.ts +417 -0
  1398. package/src/tools/security-scan.ts +287 -0
  1399. package/src/tools/shell-tokenize.ts +213 -0
  1400. package/src/tools/sqlite-reader.ts +884 -0
  1401. package/src/tools/terminal-output.ts +141 -0
  1402. package/src/tools/todo.ts +1226 -0
  1403. package/src/tools/tool-errors.ts +62 -0
  1404. package/src/tools/tool-result.ts +102 -0
  1405. package/src/tools/tool-timeouts.ts +38 -0
  1406. package/src/tools/tts.ts +266 -0
  1407. package/src/tools/vibe.ts +608 -0
  1408. package/src/tools/write.ts +1646 -0
  1409. package/src/tools/xdev.ts +560 -0
  1410. package/src/tools/yield.ts +486 -0
  1411. package/src/tts/downloader.ts +64 -0
  1412. package/src/tts/index.ts +10 -0
  1413. package/src/tts/models.ts +137 -0
  1414. package/src/tts/runtime.ts +21 -0
  1415. package/src/tts/speakable.ts +392 -0
  1416. package/src/tts/speech-enhancer.ts +206 -0
  1417. package/src/tts/streaming-player.ts +120 -0
  1418. package/src/tts/tts-client.ts +475 -0
  1419. package/src/tts/tts-protocol.ts +69 -0
  1420. package/src/tts/tts-worker.ts +434 -0
  1421. package/src/tts/vocalizer.ts +419 -0
  1422. package/src/tts/wav.ts +58 -0
  1423. package/src/tui/code-cell.ts +268 -0
  1424. package/src/tui/file-list.ts +55 -0
  1425. package/src/tui/hyperlink.ts +178 -0
  1426. package/src/tui/index.ts +13 -0
  1427. package/src/tui/output-block.ts +268 -0
  1428. package/src/tui/status-line.ts +54 -0
  1429. package/src/tui/tree-list.ts +172 -0
  1430. package/src/tui/types.ts +15 -0
  1431. package/src/tui/utils.ts +103 -0
  1432. package/src/tui/width-aware-text.ts +58 -0
  1433. package/src/utils/active-repo-context.ts +143 -0
  1434. package/src/utils/block-context.ts +312 -0
  1435. package/src/utils/changelog.ts +373 -0
  1436. package/src/utils/clipboard.ts +361 -0
  1437. package/src/utils/command-args.ts +74 -0
  1438. package/src/utils/commit-message-generator.ts +148 -0
  1439. package/src/utils/cpuprofile.ts +235 -0
  1440. package/src/utils/edit-mode.ts +61 -0
  1441. package/src/utils/enhanced-paste.ts +230 -0
  1442. package/src/utils/event-bus.ts +33 -0
  1443. package/src/utils/external-editor.ts +80 -0
  1444. package/src/utils/fetch-timeout.ts +10 -0
  1445. package/src/utils/file-display-mode.ts +44 -0
  1446. package/src/utils/file-mentions.ts +293 -0
  1447. package/src/utils/git.ts +2472 -0
  1448. package/src/utils/image-loading.ts +236 -0
  1449. package/src/utils/image-resize.ts +420 -0
  1450. package/src/utils/image-vision-fallback.ts +196 -0
  1451. package/src/utils/inspect-image-mode.ts +39 -0
  1452. package/src/utils/ipc.ts +38 -0
  1453. package/src/utils/jj.ts +422 -0
  1454. package/src/utils/lang-from-path.ts +251 -0
  1455. package/src/utils/late-cleanup.ts +17 -0
  1456. package/src/utils/local-date.ts +7 -0
  1457. package/src/utils/mac-file-urls.applescript +37 -0
  1458. package/src/utils/markit-cache.ts +166 -0
  1459. package/src/utils/markit.ts +223 -0
  1460. package/src/utils/mupdf-wasm-embed.ts +12 -0
  1461. package/src/utils/open.ts +126 -0
  1462. package/src/utils/profile-tree.ts +111 -0
  1463. package/src/utils/prompt-path.ts +3 -0
  1464. package/src/utils/qrcode.ts +535 -0
  1465. package/src/utils/sample-profile.ts +437 -0
  1466. package/src/utils/session-color.ts +142 -0
  1467. package/src/utils/shell-snapshot-fn-env.sh +63 -0
  1468. package/src/utils/shell-snapshot.ts +326 -0
  1469. package/src/utils/sixel.ts +69 -0
  1470. package/src/utils/thinking-display.ts +163 -0
  1471. package/src/utils/title-generator.ts +597 -0
  1472. package/src/utils/token-rate.ts +72 -0
  1473. package/src/utils/tool-choice.ts +50 -0
  1474. package/src/utils/tools-manager.ts +411 -0
  1475. package/src/utils/turndown.ts +82 -0
  1476. package/src/utils/zip.ts +1106 -0
  1477. package/src/vibe/runtime.ts +1684 -0
  1478. package/src/vibe/state.ts +4 -0
  1479. package/src/web/kagi.ts +305 -0
  1480. package/src/web/parallel.ts +354 -0
  1481. package/src/web/scrapers/artifacthub.ts +207 -0
  1482. package/src/web/scrapers/arxiv.ts +83 -0
  1483. package/src/web/scrapers/aur.ts +162 -0
  1484. package/src/web/scrapers/biorxiv.ts +133 -0
  1485. package/src/web/scrapers/bluesky.ts +262 -0
  1486. package/src/web/scrapers/brew.ts +172 -0
  1487. package/src/web/scrapers/cheatsh.ts +68 -0
  1488. package/src/web/scrapers/chocolatey.ts +196 -0
  1489. package/src/web/scrapers/choosealicense.ts +95 -0
  1490. package/src/web/scrapers/cisa-kev.ts +87 -0
  1491. package/src/web/scrapers/clojars.ts +154 -0
  1492. package/src/web/scrapers/coingecko.ts +177 -0
  1493. package/src/web/scrapers/crates-io.ts +97 -0
  1494. package/src/web/scrapers/crossref.ts +136 -0
  1495. package/src/web/scrapers/devto.ts +147 -0
  1496. package/src/web/scrapers/discogs.ts +306 -0
  1497. package/src/web/scrapers/discourse.ts +197 -0
  1498. package/src/web/scrapers/dockerhub.ts +138 -0
  1499. package/src/web/scrapers/docs-rs.ts +663 -0
  1500. package/src/web/scrapers/fdroid.ts +134 -0
  1501. package/src/web/scrapers/firefox-addons.ts +191 -0
  1502. package/src/web/scrapers/flathub.ts +223 -0
  1503. package/src/web/scrapers/github-gist.ts +58 -0
  1504. package/src/web/scrapers/github.ts +800 -0
  1505. package/src/web/scrapers/gitlab.ts +401 -0
  1506. package/src/web/scrapers/go-pkg.ts +266 -0
  1507. package/src/web/scrapers/hackage.ts +129 -0
  1508. package/src/web/scrapers/hackernews.ts +189 -0
  1509. package/src/web/scrapers/hex.ts +105 -0
  1510. package/src/web/scrapers/huggingface.ts +321 -0
  1511. package/src/web/scrapers/iacr.ts +89 -0
  1512. package/src/web/scrapers/index.ts +252 -0
  1513. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1514. package/src/web/scrapers/lemmy.ts +203 -0
  1515. package/src/web/scrapers/lobsters.ts +175 -0
  1516. package/src/web/scrapers/mastodon.ts +292 -0
  1517. package/src/web/scrapers/maven.ts +138 -0
  1518. package/src/web/scrapers/mdn.ts +173 -0
  1519. package/src/web/scrapers/metacpan.ts +222 -0
  1520. package/src/web/scrapers/musicbrainz.ts +250 -0
  1521. package/src/web/scrapers/npm.ts +98 -0
  1522. package/src/web/scrapers/nuget.ts +183 -0
  1523. package/src/web/scrapers/nvd.ts +222 -0
  1524. package/src/web/scrapers/ollama.ts +239 -0
  1525. package/src/web/scrapers/open-vsx.ts +106 -0
  1526. package/src/web/scrapers/opencorporates.ts +292 -0
  1527. package/src/web/scrapers/openlibrary.ts +336 -0
  1528. package/src/web/scrapers/orcid.ts +286 -0
  1529. package/src/web/scrapers/osv.ts +176 -0
  1530. package/src/web/scrapers/packagist.ts +160 -0
  1531. package/src/web/scrapers/pub-dev.ts +143 -0
  1532. package/src/web/scrapers/pubmed.ts +211 -0
  1533. package/src/web/scrapers/pypi.ts +112 -0
  1534. package/src/web/scrapers/rawg.ts +110 -0
  1535. package/src/web/scrapers/readthedocs.ts +121 -0
  1536. package/src/web/scrapers/reddit.ts +95 -0
  1537. package/src/web/scrapers/repology.ts +251 -0
  1538. package/src/web/scrapers/rfc.ts +201 -0
  1539. package/src/web/scrapers/rubygems.ts +103 -0
  1540. package/src/web/scrapers/searchcode.ts +189 -0
  1541. package/src/web/scrapers/sec-edgar.ts +261 -0
  1542. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1543. package/src/web/scrapers/snapcraft.ts +187 -0
  1544. package/src/web/scrapers/sourcegraph.ts +336 -0
  1545. package/src/web/scrapers/spdx.ts +108 -0
  1546. package/src/web/scrapers/spotify.ts +198 -0
  1547. package/src/web/scrapers/stackoverflow.ts +120 -0
  1548. package/src/web/scrapers/terraform.ts +277 -0
  1549. package/src/web/scrapers/tldr.ts +47 -0
  1550. package/src/web/scrapers/twitter.ts +94 -0
  1551. package/src/web/scrapers/types.ts +354 -0
  1552. package/src/web/scrapers/utils.ts +109 -0
  1553. package/src/web/scrapers/vimeo.ts +133 -0
  1554. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1555. package/src/web/scrapers/w3c.ts +156 -0
  1556. package/src/web/scrapers/wikidata.ts +344 -0
  1557. package/src/web/scrapers/wikipedia.ts +84 -0
  1558. package/src/web/scrapers/youtube.ts +325 -0
  1559. package/src/web/search/index.ts +393 -0
  1560. package/src/web/search/provider.ts +272 -0
  1561. package/src/web/search/providers/anthropic.ts +406 -0
  1562. package/src/web/search/providers/base.ts +112 -0
  1563. package/src/web/search/providers/brave.ts +181 -0
  1564. package/src/web/search/providers/browser-headers.ts +82 -0
  1565. package/src/web/search/providers/browser-page.ts +125 -0
  1566. package/src/web/search/providers/codex.ts +788 -0
  1567. package/src/web/search/providers/duckduckgo.ts +382 -0
  1568. package/src/web/search/providers/ecosia.ts +183 -0
  1569. package/src/web/search/providers/exa.ts +513 -0
  1570. package/src/web/search/providers/firecrawl.ts +213 -0
  1571. package/src/web/search/providers/gemini.ts +634 -0
  1572. package/src/web/search/providers/google.ts +195 -0
  1573. package/src/web/search/providers/jina.ts +144 -0
  1574. package/src/web/search/providers/kagi.ts +98 -0
  1575. package/src/web/search/providers/kimi.ts +221 -0
  1576. package/src/web/search/providers/mojeek.ts +220 -0
  1577. package/src/web/search/providers/parallel.ts +186 -0
  1578. package/src/web/search/providers/perplexity-auth.ts +142 -0
  1579. package/src/web/search/providers/perplexity.ts +997 -0
  1580. package/src/web/search/providers/public.ts +199 -0
  1581. package/src/web/search/providers/searxng.ts +467 -0
  1582. package/src/web/search/providers/startpage.ts +225 -0
  1583. package/src/web/search/providers/synthetic.ts +126 -0
  1584. package/src/web/search/providers/tavily.ts +244 -0
  1585. package/src/web/search/providers/tinyfish.ts +166 -0
  1586. package/src/web/search/providers/utils.ts +130 -0
  1587. package/src/web/search/providers/xai.ts +372 -0
  1588. package/src/web/search/providers/zai.ts +452 -0
  1589. package/src/web/search/query.ts +850 -0
  1590. package/src/web/search/render.ts +262 -0
  1591. package/src/web/search/types.ts +518 -0
  1592. package/src/web/search/utils.ts +17 -0
  1593. 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
+ }