@code-yeongyu/senpi 2026.7.20 → 2026.7.22

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 (495) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +32 -0
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +409 -68
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/auth-storage.js +1 -1
  11. package/dist/core/auth-storage.js.map +1 -1
  12. package/dist/core/compaction/compaction.d.ts +2 -2
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +67 -16
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/compaction/stream-watchdog.d.ts +31 -0
  17. package/dist/core/compaction/stream-watchdog.d.ts.map +1 -0
  18. package/dist/core/compaction/stream-watchdog.js +78 -0
  19. package/dist/core/compaction/stream-watchdog.js.map +1 -0
  20. package/dist/core/extensions/builtin/btw/index.d.ts +3 -0
  21. package/dist/core/extensions/builtin/btw/index.d.ts.map +1 -0
  22. package/dist/core/extensions/builtin/btw/index.js +120 -0
  23. package/dist/core/extensions/builtin/btw/index.js.map +1 -0
  24. package/dist/core/extensions/builtin/btw/panel.d.ts +21 -0
  25. package/dist/core/extensions/builtin/btw/panel.d.ts.map +1 -0
  26. package/dist/core/extensions/builtin/btw/panel.js +61 -0
  27. package/dist/core/extensions/builtin/btw/panel.js.map +1 -0
  28. package/dist/core/extensions/builtin/btw/side-query.d.ts +31 -0
  29. package/dist/core/extensions/builtin/btw/side-query.d.ts.map +1 -0
  30. package/dist/core/extensions/builtin/btw/side-query.js +68 -0
  31. package/dist/core/extensions/builtin/btw/side-query.js.map +1 -0
  32. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/compaction/index.js +47 -11
  34. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  35. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/openai-remote.js +3 -1
  37. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -1
  38. package/dist/core/extensions/builtin/compaction/speculative.d.ts +17 -0
  39. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/speculative.js +93 -26
  41. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  42. package/dist/core/extensions/builtin/config-reload/index.d.ts +39 -0
  43. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -0
  44. package/dist/core/extensions/builtin/config-reload/index.js +659 -0
  45. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -0
  46. package/dist/core/extensions/builtin/config-reload/log.d.ts +55 -0
  47. package/dist/core/extensions/builtin/config-reload/log.d.ts.map +1 -0
  48. package/dist/core/extensions/builtin/config-reload/log.js +128 -0
  49. package/dist/core/extensions/builtin/config-reload/log.js.map +1 -0
  50. package/dist/core/extensions/builtin/config-reload/protocol.d.ts +71 -0
  51. package/dist/core/extensions/builtin/config-reload/protocol.d.ts.map +1 -0
  52. package/dist/core/extensions/builtin/config-reload/protocol.js +83 -0
  53. package/dist/core/extensions/builtin/config-reload/protocol.js.map +1 -0
  54. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts +43 -0
  55. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts.map +1 -0
  56. package/dist/core/extensions/builtin/config-reload/watch-engine.js +298 -0
  57. package/dist/core/extensions/builtin/config-reload/watch-engine.js.map +1 -0
  58. package/dist/core/extensions/builtin/goal/command-registration.d.ts +2 -2
  59. package/dist/core/extensions/builtin/goal/command-registration.d.ts.map +1 -1
  60. package/dist/core/extensions/builtin/goal/command-registration.js +3 -4
  61. package/dist/core/extensions/builtin/goal/command-registration.js.map +1 -1
  62. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  63. package/dist/core/extensions/builtin/goal/index.js +11 -27
  64. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  65. package/dist/core/extensions/builtin/goal/tool-registration.d.ts +2 -2
  66. package/dist/core/extensions/builtin/goal/tool-registration.d.ts.map +1 -1
  67. package/dist/core/extensions/builtin/goal/tool-registration.js +2 -3
  68. package/dist/core/extensions/builtin/goal/tool-registration.js.map +1 -1
  69. package/dist/core/extensions/builtin/goal/turn-usage.d.ts +20 -0
  70. package/dist/core/extensions/builtin/goal/turn-usage.d.ts.map +1 -0
  71. package/dist/core/extensions/builtin/goal/turn-usage.js +61 -0
  72. package/dist/core/extensions/builtin/goal/turn-usage.js.map +1 -0
  73. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  74. package/dist/core/extensions/builtin/index.js +9 -0
  75. package/dist/core/extensions/builtin/index.js.map +1 -1
  76. package/dist/core/extensions/builtin/look-at/arguments.d.ts +27 -0
  77. package/dist/core/extensions/builtin/look-at/arguments.d.ts.map +1 -0
  78. package/dist/core/extensions/builtin/look-at/arguments.js +109 -0
  79. package/dist/core/extensions/builtin/look-at/arguments.js.map +1 -0
  80. package/dist/core/extensions/builtin/look-at/commands.d.ts +9 -0
  81. package/dist/core/extensions/builtin/look-at/commands.d.ts.map +1 -0
  82. package/dist/core/extensions/builtin/look-at/commands.js +102 -0
  83. package/dist/core/extensions/builtin/look-at/commands.js.map +1 -0
  84. package/dist/core/extensions/builtin/look-at/image-input.d.ts +21 -0
  85. package/dist/core/extensions/builtin/look-at/image-input.d.ts.map +1 -0
  86. package/dist/core/extensions/builtin/look-at/image-input.js +151 -0
  87. package/dist/core/extensions/builtin/look-at/image-input.js.map +1 -0
  88. package/dist/core/extensions/builtin/look-at/index.d.ts +3 -0
  89. package/dist/core/extensions/builtin/look-at/index.d.ts.map +1 -0
  90. package/dist/core/extensions/builtin/look-at/index.js +58 -0
  91. package/dist/core/extensions/builtin/look-at/index.js.map +1 -0
  92. package/dist/core/extensions/builtin/look-at/model-selector.d.ts +9 -0
  93. package/dist/core/extensions/builtin/look-at/model-selector.d.ts.map +1 -0
  94. package/dist/core/extensions/builtin/look-at/model-selector.js +77 -0
  95. package/dist/core/extensions/builtin/look-at/model-selector.js.map +1 -0
  96. package/dist/core/extensions/builtin/look-at/prompts.d.ts +5 -0
  97. package/dist/core/extensions/builtin/look-at/prompts.d.ts.map +1 -0
  98. package/dist/core/extensions/builtin/look-at/prompts.js +19 -0
  99. package/dist/core/extensions/builtin/look-at/prompts.js.map +1 -0
  100. package/dist/core/extensions/builtin/look-at/render.d.ts +26 -0
  101. package/dist/core/extensions/builtin/look-at/render.d.ts.map +1 -0
  102. package/dist/core/extensions/builtin/look-at/render.js +70 -0
  103. package/dist/core/extensions/builtin/look-at/render.js.map +1 -0
  104. package/dist/core/extensions/builtin/look-at/runner.d.ts +16 -0
  105. package/dist/core/extensions/builtin/look-at/runner.d.ts.map +1 -0
  106. package/dist/core/extensions/builtin/look-at/runner.js +108 -0
  107. package/dist/core/extensions/builtin/look-at/runner.js.map +1 -0
  108. package/dist/core/extensions/builtin/look-at/settings.d.ts +13 -0
  109. package/dist/core/extensions/builtin/look-at/settings.d.ts.map +1 -0
  110. package/dist/core/extensions/builtin/look-at/settings.js +20 -0
  111. package/dist/core/extensions/builtin/look-at/settings.js.map +1 -0
  112. package/dist/core/extensions/builtin/mcp/commands.d.ts.map +1 -1
  113. package/dist/core/extensions/builtin/mcp/commands.js +2 -0
  114. package/dist/core/extensions/builtin/mcp/commands.js.map +1 -1
  115. package/dist/core/extensions/builtin/mcp/config-schema.d.ts +6 -0
  116. package/dist/core/extensions/builtin/mcp/config-schema.d.ts.map +1 -1
  117. package/dist/core/extensions/builtin/mcp/config-schema.js +1 -0
  118. package/dist/core/extensions/builtin/mcp/config-schema.js.map +1 -1
  119. package/dist/core/extensions/builtin/mcp/config.d.ts.map +1 -1
  120. package/dist/core/extensions/builtin/mcp/config.js +9 -1
  121. package/dist/core/extensions/builtin/mcp/config.js.map +1 -1
  122. package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
  123. package/dist/core/extensions/builtin/mcp/service.js +22 -6
  124. package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
  125. package/dist/core/extensions/builtin/mcp/startup-race.d.ts +10 -0
  126. package/dist/core/extensions/builtin/mcp/startup-race.d.ts.map +1 -1
  127. package/dist/core/extensions/builtin/mcp/startup-race.js +18 -1
  128. package/dist/core/extensions/builtin/mcp/startup-race.js.map +1 -1
  129. package/dist/core/extensions/builtin/model-fallback/index.d.ts +4 -0
  130. package/dist/core/extensions/builtin/model-fallback/index.d.ts.map +1 -0
  131. package/dist/core/extensions/builtin/model-fallback/index.js +55 -0
  132. package/dist/core/extensions/builtin/model-fallback/index.js.map +1 -0
  133. package/dist/core/extensions/builtin/model-fallback/settings.d.ts +6 -0
  134. package/dist/core/extensions/builtin/model-fallback/settings.d.ts.map +1 -0
  135. package/dist/core/extensions/builtin/model-fallback/settings.js +11 -0
  136. package/dist/core/extensions/builtin/model-fallback/settings.js.map +1 -0
  137. package/dist/core/extensions/builtin/model-fallback/ui.d.ts +12 -0
  138. package/dist/core/extensions/builtin/model-fallback/ui.d.ts.map +1 -0
  139. package/dist/core/extensions/builtin/model-fallback/ui.js +72 -0
  140. package/dist/core/extensions/builtin/model-fallback/ui.js.map +1 -0
  141. package/dist/core/extensions/builtin/nested-agents-md/index.d.ts.map +1 -1
  142. package/dist/core/extensions/builtin/nested-agents-md/index.js +4 -1
  143. package/dist/core/extensions/builtin/nested-agents-md/index.js.map +1 -1
  144. package/dist/core/extensions/builtin/rules/index.d.ts.map +1 -1
  145. package/dist/core/extensions/builtin/rules/index.js +4 -1
  146. package/dist/core/extensions/builtin/rules/index.js.map +1 -1
  147. package/dist/core/extensions/builtin/terminal/output-format.d.ts +28 -0
  148. package/dist/core/extensions/builtin/terminal/output-format.d.ts.map +1 -0
  149. package/dist/core/extensions/builtin/terminal/output-format.js +74 -0
  150. package/dist/core/extensions/builtin/terminal/output-format.js.map +1 -0
  151. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +3 -0
  152. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
  153. package/dist/core/extensions/builtin/terminal/runtime-session.js +22 -2
  154. package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
  155. package/dist/core/extensions/builtin/terminal/shared.d.ts +7 -0
  156. package/dist/core/extensions/builtin/terminal/shared.d.ts.map +1 -1
  157. package/dist/core/extensions/builtin/terminal/shared.js +14 -0
  158. package/dist/core/extensions/builtin/terminal/shared.js.map +1 -1
  159. package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts +5 -1
  160. package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts.map +1 -1
  161. package/dist/core/extensions/builtin/terminal/tools/bash-output.js +88 -6
  162. package/dist/core/extensions/builtin/terminal/tools/bash-output.js.map +1 -1
  163. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +12 -1
  164. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
  165. package/dist/core/extensions/builtin/terminal/tools/bash.js +82 -8
  166. package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
  167. package/dist/core/extensions/builtin/terminal/tools/render.d.ts +16 -0
  168. package/dist/core/extensions/builtin/terminal/tools/render.d.ts.map +1 -0
  169. package/dist/core/extensions/builtin/terminal/tools/render.js +30 -0
  170. package/dist/core/extensions/builtin/terminal/tools/render.js.map +1 -0
  171. package/dist/core/extensions/builtin/terminal/tools/spawn.d.ts +2 -0
  172. package/dist/core/extensions/builtin/terminal/tools/spawn.d.ts.map +1 -1
  173. package/dist/core/extensions/builtin/terminal/tools/spawn.js +1 -1
  174. package/dist/core/extensions/builtin/terminal/tools/spawn.js.map +1 -1
  175. package/dist/core/extensions/builtin/todotools/tools/todo.d.ts.map +1 -1
  176. package/dist/core/extensions/builtin/todotools/tools/todo.js +22 -6
  177. package/dist/core/extensions/builtin/todotools/tools/todo.js.map +1 -1
  178. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.d.ts +1 -0
  179. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.d.ts.map +1 -1
  180. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.js +13 -4
  181. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.js.map +1 -1
  182. package/dist/core/extensions/builtin/webfetch/webfetch/renderers.d.ts.map +1 -1
  183. package/dist/core/extensions/builtin/webfetch/webfetch/renderers.js +17 -2
  184. package/dist/core/extensions/builtin/webfetch/webfetch/renderers.js.map +1 -1
  185. package/dist/core/extensions/builtin/webfetch/webfetch/tool.d.ts +8 -1
  186. package/dist/core/extensions/builtin/webfetch/webfetch/tool.d.ts.map +1 -1
  187. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js +45 -3
  188. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js.map +1 -1
  189. package/dist/core/extensions/builtin/websearch/websearch/renderers.d.ts.map +1 -1
  190. package/dist/core/extensions/builtin/websearch/websearch/renderers.js +13 -4
  191. package/dist/core/extensions/builtin/websearch/websearch/renderers.js.map +1 -1
  192. package/dist/core/extensions/builtin/websearch/websearch/search.d.ts +3 -2
  193. package/dist/core/extensions/builtin/websearch/websearch/search.d.ts.map +1 -1
  194. package/dist/core/extensions/builtin/websearch/websearch/search.js +2 -1
  195. package/dist/core/extensions/builtin/websearch/websearch/search.js.map +1 -1
  196. package/dist/core/extensions/builtin/websearch/websearch/tool.d.ts.map +1 -1
  197. package/dist/core/extensions/builtin/websearch/websearch/tool.js +17 -1
  198. package/dist/core/extensions/builtin/websearch/websearch/tool.js.map +1 -1
  199. package/dist/core/extensions/builtin/websearch/websearch/types.d.ts +2 -0
  200. package/dist/core/extensions/builtin/websearch/websearch/types.d.ts.map +1 -1
  201. package/dist/core/extensions/builtin/websearch/websearch/types.js.map +1 -1
  202. package/dist/core/extensions/runner.d.ts +6 -0
  203. package/dist/core/extensions/runner.d.ts.map +1 -1
  204. package/dist/core/extensions/runner.js +71 -3
  205. package/dist/core/extensions/runner.js.map +1 -1
  206. package/dist/core/extensions/types.d.ts +96 -10
  207. package/dist/core/extensions/types.d.ts.map +1 -1
  208. package/dist/core/extensions/types.js.map +1 -1
  209. package/dist/core/model-config-schema.d.ts +1942 -0
  210. package/dist/core/model-config-schema.d.ts.map +1 -0
  211. package/dist/core/model-config-schema.js +197 -0
  212. package/dist/core/model-config-schema.js.map +1 -0
  213. package/dist/core/model-config.d.ts +2 -578
  214. package/dist/core/model-config.d.ts.map +1 -1
  215. package/dist/core/model-config.js +1 -194
  216. package/dist/core/model-config.js.map +1 -1
  217. package/dist/core/model-resolver.d.ts.map +1 -1
  218. package/dist/core/model-resolver.js +1 -0
  219. package/dist/core/model-resolver.js.map +1 -1
  220. package/dist/core/model-runtime.d.ts.map +1 -1
  221. package/dist/core/model-runtime.js +9 -3
  222. package/dist/core/model-runtime.js.map +1 -1
  223. package/dist/core/provider-composer.d.ts +1 -0
  224. package/dist/core/provider-composer.d.ts.map +1 -1
  225. package/dist/core/provider-composer.js +3 -2
  226. package/dist/core/provider-composer.js.map +1 -1
  227. package/dist/core/provider-display-names.d.ts.map +1 -1
  228. package/dist/core/provider-display-names.js +1 -0
  229. package/dist/core/provider-display-names.js.map +1 -1
  230. package/dist/core/retry-fallback/chains.d.ts +29 -0
  231. package/dist/core/retry-fallback/chains.d.ts.map +1 -0
  232. package/dist/core/retry-fallback/chains.js +109 -0
  233. package/dist/core/retry-fallback/chains.js.map +1 -0
  234. package/dist/core/retry-fallback/controller.d.ts +77 -0
  235. package/dist/core/retry-fallback/controller.d.ts.map +1 -0
  236. package/dist/core/retry-fallback/controller.js +157 -0
  237. package/dist/core/retry-fallback/controller.js.map +1 -0
  238. package/dist/core/retry-fallback/cooldown.d.ts +21 -0
  239. package/dist/core/retry-fallback/cooldown.d.ts.map +1 -0
  240. package/dist/core/retry-fallback/cooldown.js +49 -0
  241. package/dist/core/retry-fallback/cooldown.js.map +1 -0
  242. package/dist/core/retry-fallback/log.d.ts +10 -0
  243. package/dist/core/retry-fallback/log.d.ts.map +1 -0
  244. package/dist/core/retry-fallback/log.js +128 -0
  245. package/dist/core/retry-fallback/log.js.map +1 -0
  246. package/dist/core/retry-fallback/validate.d.ts +8 -0
  247. package/dist/core/retry-fallback/validate.d.ts.map +1 -0
  248. package/dist/core/retry-fallback/validate.js +63 -0
  249. package/dist/core/retry-fallback/validate.js.map +1 -0
  250. package/dist/core/sdk.d.ts.map +1 -1
  251. package/dist/core/sdk.js +1 -0
  252. package/dist/core/sdk.js.map +1 -1
  253. package/dist/core/session-manager.d.ts +39 -3
  254. package/dist/core/session-manager.d.ts.map +1 -1
  255. package/dist/core/session-manager.js +125 -16
  256. package/dist/core/session-manager.js.map +1 -1
  257. package/dist/core/settings-manager.d.ts +33 -0
  258. package/dist/core/settings-manager.d.ts.map +1 -1
  259. package/dist/core/settings-manager.js +153 -6
  260. package/dist/core/settings-manager.js.map +1 -1
  261. package/dist/modes/interactive/components/footer.d.ts +2 -1
  262. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  263. package/dist/modes/interactive/components/footer.js +30 -20
  264. package/dist/modes/interactive/components/footer.js.map +1 -1
  265. package/dist/modes/interactive/components/todo-strike.d.ts +8 -0
  266. package/dist/modes/interactive/components/todo-strike.d.ts.map +1 -0
  267. package/dist/modes/interactive/components/todo-strike.js +35 -0
  268. package/dist/modes/interactive/components/todo-strike.js.map +1 -0
  269. package/dist/modes/interactive/components/tool-execution-renderer.d.ts.map +1 -1
  270. package/dist/modes/interactive/components/tool-execution-renderer.js +8 -1
  271. package/dist/modes/interactive/components/tool-execution-renderer.js.map +1 -1
  272. package/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  273. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  274. package/dist/modes/interactive/components/tool-execution.js +51 -2
  275. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  276. package/dist/modes/interactive/interactive-mode.d.ts +6 -0
  277. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  278. package/dist/modes/interactive/interactive-mode.js +124 -4
  279. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  280. package/dist/modes/interactive/tool-progress.d.ts +8 -0
  281. package/dist/modes/interactive/tool-progress.d.ts.map +1 -0
  282. package/dist/modes/interactive/tool-progress.js +26 -0
  283. package/dist/modes/interactive/tool-progress.js.map +1 -0
  284. package/dist/modes/interactive/tool-result-reveal.d.ts +18 -0
  285. package/dist/modes/interactive/tool-result-reveal.d.ts.map +1 -0
  286. package/dist/modes/interactive/tool-result-reveal.js +155 -0
  287. package/dist/modes/interactive/tool-result-reveal.js.map +1 -0
  288. package/dist/utils/fs-watch.d.ts +2 -2
  289. package/dist/utils/fs-watch.d.ts.map +1 -1
  290. package/dist/utils/fs-watch.js +2 -2
  291. package/dist/utils/fs-watch.js.map +1 -1
  292. package/docs/extensions.md +79 -1
  293. package/docs/mcp.md +10 -1
  294. package/docs/models.md +102 -1
  295. package/docs/providers.md +3 -1
  296. package/docs/settings.md +35 -0
  297. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
  298. package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
  299. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +13 -1
  300. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  301. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +76 -8
  302. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  303. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  304. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +26 -1
  305. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  306. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  307. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +24 -2
  308. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  309. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +5 -0
  310. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  311. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  312. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  313. package/node_modules/@earendil-works/pi-ai/README.md +2 -0
  314. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  315. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +68 -25
  316. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  317. package/node_modules/@earendil-works/pi-ai/dist/api/lazy.d.ts.map +1 -1
  318. package/node_modules/@earendil-works/pi-ai/dist/api/lazy.js +33 -6
  319. package/node_modules/@earendil-works/pi-ai/dist/api/lazy.js.map +1 -1
  320. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
  321. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +6 -1
  322. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  323. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts.map +1 -1
  324. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js +62 -52
  325. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js.map +1 -1
  326. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts +19 -0
  327. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts.map +1 -0
  328. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +50 -0
  329. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js.map +1 -0
  330. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts +2 -17
  331. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts.map +1 -1
  332. package/node_modules/@earendil-works/pi-ai/dist/compat.js +3 -52
  333. package/node_modules/@earendil-works/pi-ai/dist/compat.js.map +1 -1
  334. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  335. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
  336. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  337. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +45 -0
  338. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  339. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
  340. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  341. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +5 -0
  342. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  343. package/node_modules/@earendil-works/pi-ai/dist/index.js +5 -0
  344. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  345. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts +27 -0
  346. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts.map +1 -0
  347. package/node_modules/@earendil-works/pi-ai/dist/model.js +2 -0
  348. package/node_modules/@earendil-works/pi-ai/dist/model.js.map +1 -0
  349. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +1178 -1072
  350. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  351. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2 -0
  352. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  353. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.d.ts +3 -0
  354. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.d.ts.map +1 -0
  355. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.js +15 -0
  356. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.js.map +1 -0
  357. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts +64 -0
  358. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts.map +1 -0
  359. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js +5 -0
  360. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js.map +1 -0
  361. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
  362. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +2 -0
  363. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
  364. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -0
  365. package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
  366. package/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
  367. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  368. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  369. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  370. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.d.ts +1 -0
  371. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.d.ts.map +1 -1
  372. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +1 -0
  373. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js.map +1 -1
  374. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts +8 -0
  375. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts.map +1 -1
  376. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.js.map +1 -1
  377. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts +0 -4
  378. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts.map +1 -1
  379. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js.map +1 -1
  380. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +12 -4
  381. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
  382. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
  383. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +20 -4
  384. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  385. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  386. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +16 -0
  387. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  388. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  389. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.d.ts +1 -0
  390. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.d.ts.map +1 -1
  391. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js +8 -0
  392. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js.map +1 -1
  393. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.d.ts +20 -0
  394. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.d.ts.map +1 -0
  395. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.js +79 -0
  396. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.js.map +1 -0
  397. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.d.ts +2 -18
  398. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.d.ts.map +1 -1
  399. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.js +4 -218
  400. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.js.map +1 -1
  401. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.d.ts +38 -0
  402. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.d.ts.map +1 -0
  403. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.js +210 -0
  404. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.js.map +1 -0
  405. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.d.ts +9 -0
  406. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.d.ts.map +1 -0
  407. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.js +231 -0
  408. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.js.map +1 -0
  409. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.d.ts +38 -0
  410. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.d.ts.map +1 -0
  411. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.js +84 -0
  412. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.js.map +1 -0
  413. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.d.ts.map +1 -1
  414. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.js +20 -57
  415. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.js.map +1 -1
  416. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.d.ts.map +1 -1
  417. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.js +16 -99
  418. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.js.map +1 -1
  419. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.d.ts +1 -0
  420. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.d.ts.map +1 -1
  421. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.js +1 -0
  422. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.js.map +1 -1
  423. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.d.ts +5 -0
  424. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.d.ts.map +1 -0
  425. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.js +6 -0
  426. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.js.map +1 -0
  427. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.d.ts +19 -0
  428. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.d.ts.map +1 -0
  429. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.js +185 -0
  430. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.js.map +1 -0
  431. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.d.ts +11 -0
  432. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.d.ts.map +1 -0
  433. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.js +24 -0
  434. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.js.map +1 -0
  435. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.d.ts +4 -0
  436. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.d.ts.map +1 -0
  437. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.js +13 -0
  438. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.js.map +1 -0
  439. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.d.ts +15 -0
  440. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.d.ts.map +1 -0
  441. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.js +37 -0
  442. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.js.map +1 -0
  443. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.d.ts +4 -0
  444. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.d.ts.map +1 -0
  445. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.js +61 -0
  446. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.js.map +1 -0
  447. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.d.ts +29 -0
  448. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.d.ts.map +1 -0
  449. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.js +148 -0
  450. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.js.map +1 -0
  451. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.d.ts +5 -0
  452. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.d.ts.map +1 -0
  453. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.js +36 -0
  454. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.js.map +1 -0
  455. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.d.ts +15 -0
  456. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.d.ts.map +1 -0
  457. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.js +88 -0
  458. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.js.map +1 -0
  459. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.d.ts +3 -0
  460. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.d.ts.map +1 -0
  461. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.js +233 -0
  462. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.js.map +1 -0
  463. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.d.ts +23 -0
  464. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.d.ts.map +1 -0
  465. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.js +77 -0
  466. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.js.map +1 -0
  467. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.d.ts +5 -0
  468. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.d.ts.map +1 -0
  469. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.js +63 -0
  470. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.js.map +1 -0
  471. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.d.ts +13 -0
  472. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.d.ts.map +1 -0
  473. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.js +52 -0
  474. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.js.map +1 -0
  475. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.d.ts +41 -0
  476. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.d.ts.map +1 -0
  477. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.js +209 -0
  478. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.js.map +1 -0
  479. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper.d.ts.map +1 -1
  480. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper.js +70 -346
  481. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper.js.map +1 -1
  482. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +18 -23
  483. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  484. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  485. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  486. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +5 -1
  487. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  488. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.d.ts +4 -0
  489. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.d.ts.map +1 -0
  490. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.js +6 -0
  491. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.js.map +1 -0
  492. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  493. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  494. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  495. package/package.json +6 -6
@@ -16,7 +16,8 @@ import { randomUUID } from "node:crypto";
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { basename, dirname } from "node:path";
18
18
  import { prepareAgentToolCall } from "@earendil-works/pi-agent-core";
19
- import { cleanupSessionResources, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
19
+ import { cleanupSessionResources, isClassifierRefusal, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
20
+ import { getAgentDir } from "../config.js";
20
21
  import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
21
22
  import { stripFrontmatter } from "../utils/frontmatter.js";
22
23
  import { resolvePath } from "../utils/paths.js";
@@ -34,6 +35,10 @@ import { filterContextExcludedMessages } from "./messages.js";
34
35
  import { ModelRegistry } from "./model-registry.js";
35
36
  import { getModelNarrowingPatterns, resolveModelScope } from "./model-resolver.js";
36
37
  import { expandPromptTemplate } from "./prompt-templates.js";
38
+ import { RetryFallbackController } from "./retry-fallback/controller.js";
39
+ import { SelectorCooldowns } from "./retry-fallback/cooldown.js";
40
+ import { createFallbackLogger } from "./retry-fallback/log.js";
41
+ import { validateFallbackChains } from "./retry-fallback/validate.js";
37
42
  import { buildSessionContext, CURRENT_SESSION_VERSION, getLatestCompactionEntry, } from "./session-manager.js";
38
43
  import { generateSessionTitle, shouldSkipSessionTitle } from "./session-title-generator.js";
39
44
  import { SessionWorkBarrier } from "./session-work-barrier.js";
@@ -65,13 +70,39 @@ function withoutDeletedHeaders(headers) {
65
70
  ? Object.fromEntries(Object.entries(headers).filter((entry) => entry[1] !== null))
66
71
  : undefined;
67
72
  }
73
+ /**
74
+ * Human-readable rejection message paired with a `CompactionRejectionCause`.
75
+ *
76
+ * Kept exhaustive over the union so the compiler flags any new cause that would
77
+ * otherwise reintroduce silent failures at the `compaction_end` UI seam.
78
+ */
79
+ function describeCompactionRejection(cause) {
80
+ switch (cause) {
81
+ case "would-overflow":
82
+ return "Compaction rejected: the produced summary would still overflow the model context window. Reduce context (e.g. /new, drop attachments) or switch to a larger-context model.";
83
+ case "cancelled-by-extension":
84
+ return "Compaction rejected: cancelled by an extension.";
85
+ case "circuit-breaker":
86
+ return "Compaction rejected: the compaction circuit breaker is open after repeated failures. Wait for the cooldown and retry.";
87
+ case "per-turn-cap":
88
+ return "Compaction rejected: per-turn compaction cap reached for this turn.";
89
+ }
90
+ }
68
91
  class CompactionRejectedError extends Error {
69
92
  constructor(rejectionCause) {
70
- super(rejectionCause === "cancelled-by-extension" ? "Compaction cancelled" : "Compaction rejected");
93
+ super(rejectionCause === "cancelled-by-extension"
94
+ ? "Compaction cancelled"
95
+ : describeCompactionRejection(rejectionCause));
71
96
  this.name = "CompactionRejectedError";
72
97
  this.rejectionCause = rejectionCause;
73
98
  }
74
99
  }
100
+ class RequiredCompactionError extends Error {
101
+ constructor() {
102
+ super("Context remains above the compaction threshold because compaction did not complete");
103
+ this.name = "RequiredCompactionError";
104
+ }
105
+ }
75
106
  class MissingModelAccessError extends Error {
76
107
  constructor() {
77
108
  super("AgentSession requires modelRuntime or modelRegistry");
@@ -127,6 +158,7 @@ export class AgentSession {
127
158
  this._retryPromise = undefined;
128
159
  this._retryResolve = undefined;
129
160
  this._userAbortPromise = undefined;
161
+ this._suppressQueuedContinuationAfterUserAbort = false;
130
162
  // Bash execution state
131
163
  this._bashAbortController = undefined;
132
164
  this._pendingBashMessages = [];
@@ -158,6 +190,11 @@ export class AgentSession {
158
190
  this.agent = config.agent;
159
191
  this.sessionManager = config.sessionManager;
160
192
  this.settingsManager = config.settingsManager;
193
+ const noModelFallback = config.resourceLoader.getExtensions().runtime.flagValues.get("no-model-fallback") === true ||
194
+ process.env.SENPI_NO_FALLBACK === "1";
195
+ if (noModelFallback) {
196
+ this.settingsManager.applyOverrides({ retry: { modelFallback: false } });
197
+ }
161
198
  this._scopedModels = config.scopedModels ?? [];
162
199
  this._favoriteModels = config.favoriteModels ?? [];
163
200
  this._resourceLoader = config.resourceLoader;
@@ -169,6 +206,30 @@ export class AgentSession {
169
206
  }
170
207
  this._modelRuntime = modelRuntime;
171
208
  this._modelRegistry = config.modelRegistry ?? new ModelRegistry(modelRuntime);
209
+ const fallbackLogger = createFallbackLogger(config.agentDir ?? getAgentDir());
210
+ this._fallbackValidationWarnings = validateFallbackChains(this.settingsManager.getRawFallbackChains(), this._modelRegistry);
211
+ for (const warning of this._fallbackValidationWarnings) {
212
+ fallbackLogger.warn("validation_warning", { warning });
213
+ }
214
+ this._retryFallback = new RetryFallbackController({
215
+ getSettings: () => this.settingsManager.getRetryFallbackSettings(),
216
+ registry: this._modelRegistry,
217
+ cooldowns: new SelectorCooldowns(config.fallbackNow ?? (() => Date.now())),
218
+ logger: fallbackLogger,
219
+ switchModel: async (model, thinking, reason) => {
220
+ await this._switchActiveModel(model, {
221
+ persistDefault: false,
222
+ appendSessionEntry: true,
223
+ entryReason: reason,
224
+ emitModelSelect: reason === "fallback-revert",
225
+ invalidateCompaction: false,
226
+ ephemeralThinkingLevel: thinking,
227
+ });
228
+ },
229
+ emit: (event) => this._emit(event),
230
+ getCurrentSelector: () => (this.model ? { model: this.model, thinkingLevel: this.thinkingLevel } : undefined),
231
+ isAuthAvailable: (provider) => this._modelRuntime.hasConfiguredAuth(provider),
232
+ });
172
233
  this._extensionRunnerRef = config.extensionRunnerRef;
173
234
  this._initialActiveToolNames = config.initialActiveToolNames;
174
235
  this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
@@ -309,8 +370,27 @@ export class AgentSession {
309
370
  ? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
310
371
  : undefined);
311
372
  this.agent.prepareNextTurnWithContext = async (turn, signal) => {
312
- const previousSnapshot = await previousPrepareNextTurnWithContext?.(turn, signal);
313
- const previousContext = previousSnapshot?.context ?? turn.context;
373
+ let messages = turn.context.messages;
374
+ if (turn.toolResults.length > 0) {
375
+ await this._agentEventQueue;
376
+ const compacted = await this._checkCompaction(turn.message, true, "threshold");
377
+ if (compacted) {
378
+ messages = this.agent.state.messages.slice();
379
+ }
380
+ else {
381
+ const settings = this.settingsManager.getCompactionSettings();
382
+ const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
383
+ if (settings.enabled && this.model && shouldCompact(contextTokens, this.model.contextWindow, settings)) {
384
+ throw new RequiredCompactionError();
385
+ }
386
+ }
387
+ }
388
+ const postCompactionTurn = {
389
+ ...turn,
390
+ context: { ...turn.context, messages },
391
+ };
392
+ const previousSnapshot = await previousPrepareNextTurnWithContext?.(postCompactionTurn, signal);
393
+ const previousContext = previousSnapshot?.context ?? postCompactionTurn.context;
314
394
  return {
315
395
  ...previousSnapshot,
316
396
  context: {
@@ -418,7 +498,8 @@ export class AgentSession {
418
498
  return;
419
499
  }
420
500
  const lastAssistant = this._findLastAssistantInMessages(event.messages);
421
- if (!lastAssistant || !this._isRetryableError(lastAssistant)) {
501
+ if (!lastAssistant ||
502
+ (!this._isRetryableError(lastAssistant) && !this._isHardErrorFallbackEligible(lastAssistant))) {
422
503
  return;
423
504
  }
424
505
  this._retryPromise = new Promise((resolve) => {
@@ -468,12 +549,22 @@ export class AgentSession {
468
549
  return false;
469
550
  }
470
551
  const lastAssistant = this._lastAssistantMessage ?? this._findLastAssistantInMessages(messages);
471
- if (!lastAssistant || !this._isRetryableError(lastAssistant)) {
552
+ if (!lastAssistant) {
472
553
  return false;
473
554
  }
474
- if (this._retryAttempt + 1 > settings.maxRetries) {
555
+ const retryableError = this._isRetryableError(lastAssistant);
556
+ if (!retryableError && this._isHardErrorFallbackEligible(lastAssistant)) {
557
+ return true;
558
+ }
559
+ if (!retryableError) {
475
560
  return false;
476
561
  }
562
+ if (isClassifierRefusal(lastAssistant)) {
563
+ return this._retryAttempt + 1 <= settings.maxRetries && this._retryFallback.canTryFallback();
564
+ }
565
+ if (this._retryAttempt + 1 > settings.maxRetries) {
566
+ return this._retryFallback.canTryFallback();
567
+ }
477
568
  const errorMessage = lastAssistant.errorMessage || "Unknown error";
478
569
  const providerDelayMs = this._getProviderRetryDelayMs(errorMessage);
479
570
  if (providerDelayMs === undefined) {
@@ -486,6 +577,7 @@ export class AgentSession {
486
577
  // This ensures the UI sees the updated queue state
487
578
  if (event.type === "message_start" && event.message.role === "user") {
488
579
  this._overflowRecoveryAttempted = false;
580
+ this._retryFallback.resetTurn();
489
581
  const messageText = this._getUserMessageText(event.message);
490
582
  if (messageText) {
491
583
  // Check steering queue first
@@ -534,6 +626,14 @@ export class AgentSession {
534
626
  // Reset retry counter immediately on successful assistant response
535
627
  // This prevents accumulation across multiple LLM calls within a turn
536
628
  if (assistantMsg.stopReason !== "error" && this._retryAttempt > 0) {
629
+ const fallback = this._retryFallback.activeState;
630
+ if (fallback) {
631
+ this._emit({
632
+ type: "retry_fallback_succeeded",
633
+ model: this.model ? `${this.model.provider}/${this.model.id}` : "",
634
+ chainKey: fallback.chainKey,
635
+ });
636
+ }
537
637
  this._emit({
538
638
  type: "auto_retry_end",
539
639
  success: true,
@@ -545,16 +645,27 @@ export class AgentSession {
545
645
  }
546
646
  // Check auto-retry and auto-compaction after agent completes
547
647
  let launchedContinuation = false;
548
- const allowsQueuedContinuation = event.type === "agent_end" ? this._agentEndAllowsQueuedContinuation(event.messages) : false;
648
+ const userAbortSuppressedQueuedContinuation = event.type === "agent_end" && this._suppressQueuedContinuationAfterUserAbort;
649
+ if (userAbortSuppressedQueuedContinuation) {
650
+ this._suppressQueuedContinuationAfterUserAbort = false;
651
+ }
652
+ const allowsQueuedContinuation = event.type === "agent_end" && !userAbortSuppressedQueuedContinuation
653
+ ? this._agentEndAllowsQueuedContinuation(event.messages)
654
+ : false;
549
655
  if (event.type === "agent_end" && this._lastAssistantMessage) {
550
656
  const msg = this._lastAssistantMessage;
551
657
  this._lastAssistantMessage = undefined;
552
- // Check for retryable errors first (overloaded, rate limit, server errors)
553
- if (this._isRetryableError(msg)) {
554
- const didRetry = await this._handleRetryableError(msg);
555
- if (didRetry)
556
- return; // Retry was initiated, don't proceed to compaction
658
+ // Retry transient failures normally and eligible hard errors only through a fallback.
659
+ const retryableError = this._isRetryableError(msg);
660
+ let didRetry = false;
661
+ if (retryableError) {
662
+ didRetry = await this._handleRetryableError(msg);
663
+ }
664
+ else if (this._isHardErrorFallbackEligible(msg)) {
665
+ didRetry = await this._handleRetryableError(msg, { hardErrorFallback: true });
557
666
  }
667
+ if (didRetry)
668
+ return; // Retry was initiated, don't proceed to compaction
558
669
  this._resolveRetry();
559
670
  launchedContinuation = await this._checkCompaction(msg);
560
671
  }
@@ -598,6 +709,10 @@ export class AgentSession {
598
709
  }
599
710
  return undefined;
600
711
  }
712
+ _isAssistantFromBeforeLatestCompaction(assistantMessage) {
713
+ const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
714
+ return compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
715
+ }
601
716
  _replaceMessageInPlace(target, replacement) {
602
717
  // Agent-core stores the finalized message object in its state before emitting message_end.
603
718
  // SessionManager persistence happens later in _processAgentEvent() with event.message.
@@ -1048,11 +1163,22 @@ export class AgentSession {
1048
1163
  throwIfCancelled();
1049
1164
  }
1050
1165
  const shouldWaitForSessionWork = options?.source !== "extension";
1166
+ // A steer/followUp submission during an active run can be queued immediately.
1167
+ // Waiting on the session-work barrier here would trap the message inside this
1168
+ // call for the rest of the run whenever a queued continuation (e.g. an active
1169
+ // goal chain) holds the barrier, making typed input invisible until the run
1170
+ // ends or the user aborts.
1171
+ const canQueueWhileStreaming = this.isStreaming && !this.isCompacting && options?.streamingBehavior !== undefined;
1051
1172
  if (shouldWaitForSessionWork &&
1173
+ !canQueueWhileStreaming &&
1052
1174
  (!this.isStreaming || this.isCompacting || this._sessionWorkBarrier.hasActiveWork)) {
1053
1175
  await this._waitForSettledSessionWork();
1054
1176
  throwIfCancelled();
1055
1177
  }
1178
+ // Turn boundary: restore the primary model if the fallback cooldown expired.
1179
+ if (!this.isStreaming) {
1180
+ await this._maybeRestoreFallbackPrimary();
1181
+ }
1056
1182
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
1057
1183
  const preflightResult = options?.preflightResult;
1058
1184
  const promptDisposition = options?.promptDisposition;
@@ -1109,6 +1235,27 @@ export class AgentSession {
1109
1235
  preflightResult?.(true);
1110
1236
  return;
1111
1237
  }
1238
+ // The queue-while-streaming bypass above skipped the settled-work wait. If
1239
+ // the run ended while input was being expanded, serialize with remaining
1240
+ // session work before sending a fresh prompt, and re-queue if a scheduled
1241
+ // continuation started a new run in the meantime.
1242
+ if (canQueueWhileStreaming &&
1243
+ shouldWaitForSessionWork &&
1244
+ (this.isCompacting || this._sessionWorkBarrier.hasActiveWork)) {
1245
+ await this._waitForSettledSessionWork();
1246
+ throwIfCancelled();
1247
+ if (this.isStreaming) {
1248
+ if (options?.streamingBehavior === "followUp") {
1249
+ await this._queueFollowUp(expandedText, currentImages);
1250
+ }
1251
+ else {
1252
+ await this._queueSteer(expandedText, currentImages);
1253
+ }
1254
+ promptDisposition?.("queued");
1255
+ preflightResult?.(true);
1256
+ return;
1257
+ }
1258
+ }
1112
1259
  // Flush any pending bash messages before the new prompt
1113
1260
  this._flushPendingBashMessages();
1114
1261
  // Validate model
@@ -1130,7 +1277,14 @@ export class AgentSession {
1130
1277
  // The user's new prompt is sent below, so do not call agent.continue() here.
1131
1278
  const lastAssistant = this._findLastAssistantMessage();
1132
1279
  if (lastAssistant) {
1133
- await this._checkCompaction(lastAssistant, false, "pre_prompt");
1280
+ const compacted = await this._checkCompaction(lastAssistant, false, "pre_prompt");
1281
+ if (!compacted && !this._isAssistantFromBeforeLatestCompaction(lastAssistant)) {
1282
+ const settings = this.settingsManager.getCompactionSettings();
1283
+ const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
1284
+ if (settings.enabled && this.model && shouldCompact(contextTokens, this.model.contextWindow, settings)) {
1285
+ throw new RequiredCompactionError();
1286
+ }
1287
+ }
1134
1288
  }
1135
1289
  // Build messages array (custom message if any, then user message)
1136
1290
  messages = [];
@@ -1525,6 +1679,9 @@ export class AgentSession {
1525
1679
  await this._userAbortPromise;
1526
1680
  return;
1527
1681
  }
1682
+ if (this.isStreaming) {
1683
+ this._suppressQueuedContinuationAfterUserAbort = true;
1684
+ }
1528
1685
  const abortPromise = (async () => {
1529
1686
  this.agent.abort();
1530
1687
  await this.waitForIdle();
@@ -1592,20 +1749,57 @@ export class AgentSession {
1592
1749
  if (!(await this._modelRuntime.checkAuth(model.provider))) {
1593
1750
  throw new Error(`No API key for ${model.provider}/${model.id}`);
1594
1751
  }
1752
+ // A manual model change abandons any active fallback window; if a fallback
1753
+ // retry sleep is still pending, cancel it so no surprise continuation fires.
1754
+ const hadActiveFallback = this._retryFallback.activeState !== undefined;
1755
+ this._retryFallback.clearForManualModelChange(model);
1756
+ if (hadActiveFallback && this._retryAbortController) {
1757
+ this.abortRetry();
1758
+ }
1759
+ return await this._switchActiveModel(model, {
1760
+ persistDefault: true,
1761
+ appendSessionEntry: true,
1762
+ emitModelSelect: true,
1763
+ invalidateCompaction: true,
1764
+ });
1765
+ }
1766
+ async _maybeRestoreFallbackPrimary() {
1767
+ try {
1768
+ await this._retryFallback.maybeRestorePrimary(this.settingsManager.getRetryFallbackSettings().revertPolicy);
1769
+ }
1770
+ catch (error) {
1771
+ this._retryFallback.clear();
1772
+ console.error("fallback revert failed; cleared fallback state", error);
1773
+ }
1774
+ }
1775
+ async _switchActiveModel(model, opts) {
1595
1776
  const previousModel = this.model;
1596
- const invalidatesCompaction = this._modelSelectionChangesContext(previousModel, model);
1597
- if (invalidatesCompaction) {
1777
+ if (opts.invalidateCompaction && this._modelSelectionChangesContext(previousModel, model)) {
1598
1778
  this._invalidateCompactionForModelSelection();
1599
1779
  }
1600
- const thinkingLevel = this._getThinkingLevelForModelSwitch();
1780
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(opts.ephemeralThinkingLevel);
1601
1781
  this.agent.state.model = model;
1602
- this.sessionManager.appendModelChange(model.provider, model.id);
1603
- this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
1782
+ if (opts.appendSessionEntry) {
1783
+ this.sessionManager.appendModelChange(model.provider, model.id, opts.entryReason, previousModel?.provider, previousModel?.id);
1784
+ }
1785
+ if (opts.persistDefault) {
1786
+ this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
1787
+ }
1604
1788
  const scopedMatch = this._scopedModels.find((sm) => modelsAreEqual(sm.model, model));
1605
1789
  this._currentServiceTier = this._resolveServiceTier(model, scopedMatch?.serviceTier);
1606
- this.setThinkingLevel(thinkingLevel);
1790
+ if (opts.ephemeralThinkingLevel !== undefined) {
1791
+ this._applyEphemeralThinkingLevel(thinkingLevel);
1792
+ }
1793
+ else {
1794
+ this.setThinkingLevel(thinkingLevel);
1795
+ }
1796
+ if (!opts.emitModelSelect)
1797
+ return undefined;
1607
1798
  return await this._emitModelSelect(model, previousModel, "set");
1608
1799
  }
1800
+ _applyEphemeralThinkingLevel(level) {
1801
+ this.agent.state.thinkingLevel = level;
1802
+ }
1609
1803
  /**
1610
1804
  * Cycle to next/previous model.
1611
1805
  * Uses favorite models, constrained by any global model narrowing.
@@ -1637,6 +1831,7 @@ export class AgentSession {
1637
1831
  if (invalidatesCompaction) {
1638
1832
  this._invalidateCompactionForModelSelection();
1639
1833
  }
1834
+ this._retryFallback.clearForManualModelChange(next.model);
1640
1835
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1641
1836
  this.agent.state.model = next.model;
1642
1837
  this.sessionManager.appendModelChange(next.model.provider, next.model.id);
@@ -1668,6 +1863,9 @@ export class AgentSession {
1668
1863
  // Only persist if actually changing
1669
1864
  const previousLevel = this.agent.state.thinkingLevel;
1670
1865
  const isChanging = effectiveLevel !== previousLevel;
1866
+ if (isChanging) {
1867
+ this._retryFallback.noteManualThinkingLevel();
1868
+ }
1671
1869
  this.agent.state.thinkingLevel = effectiveLevel;
1672
1870
  if (isChanging) {
1673
1871
  this.sessionManager.appendThinkingLevelChange(effectiveLevel);
@@ -1919,7 +2117,7 @@ export class AgentSession {
1919
2117
  signal,
1920
2118
  }));
1921
2119
  if (extensionResult?.cancel) {
1922
- return await this._rejectCompaction(request, requestId, "cancelled-by-extension", true);
2120
+ return await this._rejectCompaction(request, requestId, extensionResult.rejectionCause ?? "cancelled-by-extension", true, extensionResult.reason);
1923
2121
  }
1924
2122
  if (extensionResult?.compaction) {
1925
2123
  compactionResult = extensionResult.compaction;
@@ -1928,7 +2126,7 @@ export class AgentSession {
1928
2126
  }
1929
2127
  if (!compactionResult) {
1930
2128
  const { apiKey, headers, extraBody, env } = await this._getCompactionRequestAuth(this.model);
1931
- compactionResult = await compact(preparation, this.model, apiKey, headers, request.customInstructions, signal, extraBody, this.thinkingLevel, this.agent.streamFn, env);
2129
+ compactionResult = await compact(preparation, this.model, apiKey, headers, request.customInstructions, signal, extraBody, this.thinkingLevel, this.agent.streamFn, env, this.agent.transformContext);
1932
2130
  }
1933
2131
  }
1934
2132
  if (signal.aborted) {
@@ -1991,11 +2189,22 @@ export class AgentSession {
1991
2189
  fromHook: fromExtension,
1992
2190
  };
1993
2191
  const simulatedMessages = buildSessionContext([...pathEntries, simulatedCompactionEntry], simulatedCompactionEntry.id).messages;
1994
- const contextTokens = estimateContextTokens(filterContextExcludedMessages(simulatedMessages)).tokens;
2192
+ const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
1995
2193
  const settings = this.settingsManager.getCompactionSettings();
1996
2194
  return contextTokens > this.model.contextWindow - settings.reserveTokens;
1997
2195
  }
1998
- async _rejectCompaction(request, requestId, rejectionCause, aborted) {
2196
+ async _rejectCompaction(request, requestId, rejectionCause, aborted, extensionReason) {
2197
+ // Per plan Section 1: rejection must never be silent. The compaction_end event
2198
+ // carries a non-empty human-readable errorMessage (unless the user aborted, where
2199
+ // the aborted branch already renders "Compaction cancelled"). session_compact is
2200
+ // also emitted with accepted:false so the compaction extension's circuit-breaker
2201
+ // bookkeeping stops being dead code; other builtin session_compact handlers guard
2202
+ // on event.accepted.
2203
+ const trimmedExtensionReason = extensionReason?.trim();
2204
+ const detailedMessage = trimmedExtensionReason
2205
+ ? `Compaction rejected: ${trimmedExtensionReason}`
2206
+ : describeCompactionRejection(rejectionCause);
2207
+ const errorMessage = aborted && !trimmedExtensionReason ? undefined : detailedMessage;
1999
2208
  this._emit({
2000
2209
  type: "compaction_end",
2001
2210
  reason: request.reason,
@@ -2005,6 +2214,16 @@ export class AgentSession {
2005
2214
  requestId,
2006
2215
  accepted: false,
2007
2216
  rejectionCause,
2217
+ errorMessage,
2218
+ });
2219
+ await this._extensionRunner.emit({
2220
+ type: "session_compact",
2221
+ reason: request.reason,
2222
+ requestId,
2223
+ accepted: false,
2224
+ rejectionCause,
2225
+ fromExtension: false,
2226
+ willRetry: false,
2008
2227
  });
2009
2228
  return { accepted: false, requestId, rejectionCause };
2010
2229
  }
@@ -2032,7 +2251,7 @@ export class AgentSession {
2032
2251
  * @param assistantMessage The assistant message to check
2033
2252
  * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
2034
2253
  */
2035
- async _checkCompaction(assistantMessage, skipAbortedCheck = true, requestReason) {
2254
+ async _checkCompaction(assistantMessage, skipAbortedCheck = true, inlineReason) {
2036
2255
  const settings = this.settingsManager.getCompactionSettings();
2037
2256
  if (!settings.enabled)
2038
2257
  return false;
@@ -2050,8 +2269,7 @@ export class AgentSession {
2050
2269
  // compaction boundary. This prevents a stale pre-compaction usage/error
2051
2270
  // from retriggering compaction on the first prompt after compaction.
2052
2271
  const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
2053
- const assistantIsFromBeforeCompaction = compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
2054
- if (assistantIsFromBeforeCompaction) {
2272
+ if (this._isAssistantFromBeforeLatestCompaction(assistantMessage)) {
2055
2273
  return false;
2056
2274
  }
2057
2275
  // Case 1: Overflow - LLM returned context overflow error.
@@ -2076,7 +2294,7 @@ export class AgentSession {
2076
2294
  willRetry: false,
2077
2295
  errorMessage,
2078
2296
  });
2079
- if (requestReason === "pre_prompt") {
2297
+ if (inlineReason === "pre_prompt") {
2080
2298
  throw new Error(errorMessage);
2081
2299
  }
2082
2300
  return false;
@@ -2089,42 +2307,47 @@ export class AgentSession {
2089
2307
  this.agent.state.messages = messages.slice(0, -1);
2090
2308
  this._incrementMessageRevision();
2091
2309
  }
2092
- if (requestReason) {
2093
- await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, "overflow", willRetry);
2310
+ if (inlineReason) {
2311
+ return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, "overflow", willRetry);
2094
2312
  }
2095
2313
  else {
2096
2314
  return await this._runAutoCompaction("overflow", willRetry);
2097
2315
  }
2098
- return false;
2099
2316
  }
2100
2317
  // Case 2: Threshold - context is getting large
2101
2318
  // For error messages or all-zero usage messages, estimate from the last valid response.
2102
2319
  // This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
2103
2320
  // responses can still compact and do not reset context accounting.
2104
2321
  let contextTokens;
2105
- const directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;
2106
- if (assistantMessage.stopReason === "error" || directContextTokens === 0) {
2107
- const messages = filterContextExcludedMessages(this.agent.state.messages);
2108
- const estimate = estimateContextTokens(messages);
2109
- if (estimate.lastUsageIndex === null)
2110
- return false; // No usage data at all
2111
- // Verify the usage source is post-compaction. Kept pre-compaction messages
2112
- // have stale usage reflecting the old (larger) context and would falsely
2113
- // trigger compaction right after one just finished.
2114
- const usageMsg = messages[estimate.lastUsageIndex];
2115
- if (compactionEntry &&
2116
- usageMsg.role === "assistant" &&
2117
- usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
2118
- return false;
2119
- }
2120
- contextTokens = estimate.tokens;
2322
+ if (inlineReason) {
2323
+ const messages = filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages);
2324
+ contextTokens = estimateContextTokens(messages).tokens;
2121
2325
  }
2122
2326
  else {
2123
- contextTokens = directContextTokens;
2327
+ const directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;
2328
+ if (assistantMessage.stopReason !== "error" && directContextTokens !== 0) {
2329
+ contextTokens = directContextTokens;
2330
+ }
2331
+ else {
2332
+ const messages = filterContextExcludedMessages(this.agent.state.messages);
2333
+ const estimate = estimateContextTokens(messages);
2334
+ if (estimate.lastUsageIndex === null)
2335
+ return false; // No usage data at all
2336
+ // Verify the usage source is post-compaction. Kept pre-compaction messages
2337
+ // have stale usage reflecting the old (larger) context and would falsely
2338
+ // trigger compaction right after one just finished.
2339
+ const usageMsg = messages[estimate.lastUsageIndex];
2340
+ if (compactionEntry &&
2341
+ usageMsg.role === "assistant" &&
2342
+ usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
2343
+ return false;
2344
+ }
2345
+ contextTokens = estimate.tokens;
2346
+ }
2124
2347
  }
2125
2348
  if (shouldCompact(contextTokens, contextWindow, settings)) {
2126
- if (requestReason) {
2127
- await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck);
2349
+ if (inlineReason) {
2350
+ return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason);
2128
2351
  }
2129
2352
  else {
2130
2353
  return await this._runAutoCompaction("threshold", false);
@@ -2145,6 +2368,7 @@ export class AgentSession {
2145
2368
  if (!execution.accepted && isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
2146
2369
  this._overflowRecoveryAttempted = false;
2147
2370
  }
2371
+ return execution.accepted;
2148
2372
  }
2149
2373
  catch (error) {
2150
2374
  if (isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
@@ -2160,6 +2384,7 @@ export class AgentSession {
2160
2384
  willRetry,
2161
2385
  errorMessage: aborted ? undefined : `Pre-prompt compaction failed: ${errorMessage}`,
2162
2386
  });
2387
+ return false;
2163
2388
  }
2164
2389
  finally {
2165
2390
  this._compactionAbortController = undefined;
@@ -2477,11 +2702,55 @@ export class AgentSession {
2477
2702
  void this.abort();
2478
2703
  },
2479
2704
  hasPendingMessages: () => this.pendingMessageCount > 0,
2705
+ isCompacting: () => this.isCompacting,
2480
2706
  shutdown: () => {
2481
2707
  this._extensionShutdownHandler?.();
2482
2708
  },
2483
2709
  getContextUsage: () => this.getContextUsage(),
2484
2710
  getCompactionSettings: () => this.settingsManager.getCompactionSettings(),
2711
+ getLookAtSettings: () => {
2712
+ const global = this.settingsManager.getGlobalSettings().lookAt;
2713
+ const project = this.settingsManager.getProjectSettings().lookAt;
2714
+ return {
2715
+ enabled: project?.enabled ?? global?.enabled ?? true,
2716
+ models: project?.models ?? global?.models,
2717
+ };
2718
+ },
2719
+ getImageSettings: () => ({
2720
+ autoResize: this.settingsManager.getImageAutoResize(),
2721
+ blockImages: this.settingsManager.getBlockImages(),
2722
+ }),
2723
+ sessionSettings: {
2724
+ getRetryFallbackSettings: () => this.settingsManager.getRetryFallbackSettings(),
2725
+ setFallbackChain: async (key, entries) => {
2726
+ this.settingsManager.setFallbackChain(key, [...entries]);
2727
+ await this.settingsManager.flush();
2728
+ },
2729
+ removeFallbackChain: async (key) => {
2730
+ this.settingsManager.removeFallbackChain(key);
2731
+ await this.settingsManager.flush();
2732
+ },
2733
+ setModelFallbackEnabled: async (enabled) => {
2734
+ this.settingsManager.setModelFallbackEnabled(enabled);
2735
+ await this.settingsManager.flush();
2736
+ },
2737
+ setFallbackRevertPolicy: async (policy) => {
2738
+ this.settingsManager.setFallbackRevertPolicy(policy);
2739
+ await this.settingsManager.flush();
2740
+ },
2741
+ reload: () => this.settingsManager.reload(),
2742
+ getFallbackStatus: () => {
2743
+ const active = this._retryFallback.activeState;
2744
+ if (!active)
2745
+ return undefined;
2746
+ return {
2747
+ active: true,
2748
+ currentModel: this.model ? `${this.model.provider}/${this.model.id}` : undefined,
2749
+ originalSelector: active.originalSelector,
2750
+ pinned: active.pinned,
2751
+ };
2752
+ },
2753
+ },
2485
2754
  compact: (options) => {
2486
2755
  void (async () => {
2487
2756
  this._disconnectFromAgent();
@@ -2553,6 +2822,10 @@ export class AgentSession {
2553
2822
  },
2554
2823
  });
2555
2824
  }
2825
+ /** Fallback-chain configuration warnings calculated when this session started. */
2826
+ get fallbackValidationWarnings() {
2827
+ return this._fallbackValidationWarnings;
2828
+ }
2556
2829
  _refreshToolRegistry(options) {
2557
2830
  const previousRegistryNames = new Set(this._toolRegistry.keys());
2558
2831
  const previousActiveToolNames = this.getActiveToolNames();
@@ -2700,16 +2973,25 @@ export class AgentSession {
2700
2973
  * Context overflow errors are NOT retryable (handled by compaction instead).
2701
2974
  */
2702
2975
  _isRetryableError(message) {
2703
- if (!message.errorMessage)
2704
- return false;
2705
2976
  // Context overflow is handled by compaction, not retry.
2706
2977
  if (isContextOverflow(message, this.model?.contextWindow ?? 0))
2707
2978
  return false;
2979
+ if (isClassifierRefusal(message))
2980
+ return true;
2981
+ if (!message.errorMessage)
2982
+ return false;
2708
2983
  if (message.stopReason === "aborted") {
2709
2984
  return /timed? out|timeout/i.test(message.errorMessage);
2710
2985
  }
2711
2986
  return isRetryableAssistantError(message);
2712
2987
  }
2988
+ _isHardErrorFallbackEligible(message) {
2989
+ return (message.stopReason === "error" &&
2990
+ !isContextOverflow(message, this.model?.contextWindow ?? 0) &&
2991
+ !isClassifierRefusal(message) &&
2992
+ !message.content.some((content) => content.type === "toolCall") &&
2993
+ this._retryFallback.canTryFallback());
2994
+ }
2713
2995
  _getProviderRetryDelayMs(errorMessage) {
2714
2996
  const retryAfterMsMatch = errorMessage.match(/\bretry[-_ ]?after[-_ ]?ms\s*[:=]\s*(\d+(?:\.\d+)?)/i);
2715
2997
  if (retryAfterMsMatch) {
@@ -2742,7 +3024,7 @@ export class AgentSession {
2742
3024
  * Handle retryable errors with exponential backoff.
2743
3025
  * @returns true if retry was initiated, false if max retries exceeded or disabled
2744
3026
  */
2745
- async _handleRetryableError(message) {
3027
+ async _handleRetryableError(message, options = {}) {
2746
3028
  const settings = this.settingsManager.getRetrySettings();
2747
3029
  if (!settings.enabled) {
2748
3030
  this._resolveRetry();
@@ -2755,21 +3037,71 @@ export class AgentSession {
2755
3037
  this._retryResolve = resolve;
2756
3038
  });
2757
3039
  }
2758
- this._retryAttempt++;
2759
- if (this._retryAttempt > settings.maxRetries) {
2760
- // Max retries exceeded, emit final failure and reset
2761
- this._emit({
2762
- type: "auto_retry_end",
2763
- success: false,
2764
- attempt: this._retryAttempt - 1,
2765
- finalError: message.errorMessage,
2766
- });
2767
- this._retryAttempt = 0;
2768
- this._resolveRetry(); // Resolve so waitForRetry() completes
2769
- return false;
2770
- }
2771
3040
  const errorMessage = message.errorMessage || "Unknown error";
2772
- const providerDelayMs = this._getProviderRetryDelayMs(errorMessage);
3041
+ const isRefusal = isClassifierRefusal(message);
3042
+ const hardErrorFallback = options.hardErrorFallback === true;
3043
+ let switchedFallback = false;
3044
+ if (hardErrorFallback) {
3045
+ // A non-retryable provider failure must never replay on the same model.
3046
+ switchedFallback = await this._retryFallback.tryFallback("hard-error", { errorMessage });
3047
+ if (!switchedFallback) {
3048
+ this._resolveRetry();
3049
+ return false;
3050
+ }
3051
+ // The fallback starts fresh; the failed model's transient attempts do not carry over.
3052
+ this._retryAttempt = 1;
3053
+ }
3054
+ else if (isRefusal) {
3055
+ // Refusals are only retried through a new chain candidate. They never use
3056
+ // same-model retries or the transient over-budget fallback escape hatch.
3057
+ if (this._retryAttempt + 1 > settings.maxRetries) {
3058
+ this._resolveRetry();
3059
+ return false;
3060
+ }
3061
+ switchedFallback = await this._retryFallback.tryFallback("refusal", {});
3062
+ if (!switchedFallback) {
3063
+ const exhaustedChainKey = this._retryFallback.exhaustedChainKey;
3064
+ if (exhaustedChainKey) {
3065
+ this._emit({ type: "retry_fallback_exhausted", chainKey: exhaustedChainKey, lastError: errorMessage });
3066
+ }
3067
+ this._resolveRetry();
3068
+ return false;
3069
+ }
3070
+ this._retryAttempt++;
3071
+ }
3072
+ else {
3073
+ this._retryAttempt++;
3074
+ if (this._retryAttempt > settings.maxRetries) {
3075
+ switchedFallback = await this._retryFallback.tryFallback("transient", {
3076
+ errorMessage,
3077
+ retryAfterMs: this._getProviderRetryDelayMs(errorMessage),
3078
+ });
3079
+ if (switchedFallback) {
3080
+ // The new model receives a fresh retry budget; the failed model does not.
3081
+ this._retryAttempt = 1;
3082
+ }
3083
+ else {
3084
+ const exhaustedChainKey = this._retryFallback.exhaustedChainKey;
3085
+ if (exhaustedChainKey) {
3086
+ this._emit({
3087
+ type: "retry_fallback_exhausted",
3088
+ chainKey: exhaustedChainKey,
3089
+ lastError: errorMessage,
3090
+ });
3091
+ }
3092
+ this._emit({
3093
+ type: "auto_retry_end",
3094
+ success: false,
3095
+ attempt: this._retryAttempt - 1,
3096
+ finalError: message.errorMessage,
3097
+ });
3098
+ this._retryAttempt = 0;
3099
+ this._resolveRetry();
3100
+ return false;
3101
+ }
3102
+ }
3103
+ }
3104
+ const providerDelayMs = isRefusal || hardErrorFallback ? undefined : this._getProviderRetryDelayMs(errorMessage);
2773
3105
  const maxRetryDelayMs = this.settingsManager.getProviderRetrySettings().maxRetryDelayMs;
2774
3106
  if (providerDelayMs !== undefined && providerDelayMs > maxRetryDelayMs) {
2775
3107
  this._emit({
@@ -2782,7 +3114,13 @@ export class AgentSession {
2782
3114
  this._resolveRetry();
2783
3115
  return false;
2784
3116
  }
2785
- const delayMs = providerDelayMs ?? settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
3117
+ if (!switchedFallback) {
3118
+ switchedFallback = await this._retryFallback.tryFallback("transient", {
3119
+ errorMessage,
3120
+ retryAfterMs: providerDelayMs,
3121
+ });
3122
+ }
3123
+ const delayMs = switchedFallback ? 0 : (providerDelayMs ?? settings.baseDelayMs * 2 ** (this._retryAttempt - 1));
2786
3124
  // Prepare before auto_retry_start so an immediate Esc can cancel the retry sleep.
2787
3125
  this._retryAbortController = new AbortController();
2788
3126
  this._emit({
@@ -2818,6 +3156,9 @@ export class AgentSession {
2818
3156
  return false;
2819
3157
  }
2820
3158
  this._retryAbortController = undefined;
3159
+ // Turn boundary: a suppression that expired during the backoff sleep lets
3160
+ // the retry continue on the restored primary instead of the fallback model.
3161
+ await this._maybeRestoreFallbackPrimary();
2821
3162
  // Retry via continue() - use setTimeout to break out of event handler chain
2822
3163
  setTimeout(() => {
2823
3164
  this.agent.continue().catch(() => {