@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
@@ -0,0 +1,8 @@
1
+ export interface ToolProgressDetails {
2
+ readonly activity?: string;
3
+ readonly startedAt: number;
4
+ readonly maxWaitMs?: number;
5
+ }
6
+ export declare function readToolProgress(details: unknown): ToolProgressDetails | undefined;
7
+ export declare function formatToolProgressLine(progress: ToolProgressDetails, now: number, spinnerFrame?: number): string;
8
+ //# sourceMappingURL=tool-progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-progress.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/tool-progress.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB,GAAG,SAAS,CAYlF;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAOhH"}
@@ -0,0 +1,26 @@
1
+ import { formatWorkingElapsedSeconds } from "./working-status.js";
2
+ export function readToolProgress(details) {
3
+ if (typeof details !== "object" || details === null || !("progress" in details))
4
+ return undefined;
5
+ const progress = details.progress;
6
+ if (typeof progress !== "object" || progress === null || !("startedAt" in progress))
7
+ return undefined;
8
+ if (typeof progress.startedAt !== "number" || !Number.isFinite(progress.startedAt))
9
+ return undefined;
10
+ if ("activity" in progress && typeof progress.activity !== "string")
11
+ return undefined;
12
+ if ("maxWaitMs" in progress && (typeof progress.maxWaitMs !== "number" || !Number.isFinite(progress.maxWaitMs))) {
13
+ return undefined;
14
+ }
15
+ const activity = "activity" in progress && typeof progress.activity === "string" ? progress.activity : undefined;
16
+ const maxWaitMs = "maxWaitMs" in progress && typeof progress.maxWaitMs === "number" ? progress.maxWaitMs : undefined;
17
+ return { activity, startedAt: progress.startedAt, maxWaitMs };
18
+ }
19
+ export function formatToolProgressLine(progress, now, spinnerFrame) {
20
+ const activity = progress.activity || "working";
21
+ const elapsed = formatWorkingElapsedSeconds((now - progress.startedAt) / 1_000);
22
+ const maxWait = progress.maxWaitMs === undefined ? "" : ` / max ${Math.max(0, Math.floor(progress.maxWaitMs / 1_000))}s`;
23
+ const spinner = spinnerFrame === undefined ? "⏵" : ["⏵", "⏷", "⏴", "⏶"][spinnerFrame % 4];
24
+ return `${spinner} ${activity} · ${elapsed}${maxWait}`;
25
+ }
26
+ //# sourceMappingURL=tool-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-progress.js","sourceRoot":"","sources":["../../../src/modes/interactive/tool-progress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAQlE,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAChD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAClG,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IACtG,IAAI,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACrG,IAAI,UAAU,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtF,IAAI,WAAW,IAAI,QAAQ,IAAI,CAAC,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QACjH,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACjH,MAAM,SAAS,GAAG,WAAW,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACrH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,QAA6B,EAAE,GAAW,EAAE,YAAqB;IACvG,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,2BAA2B,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,CAAC;IAChF,MAAM,OAAO,GACZ,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC;IAC1G,MAAM,OAAO,GAAG,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;IAC1F,OAAO,GAAG,OAAO,IAAI,QAAQ,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC;AACxD,CAAC","sourcesContent":["import { formatWorkingElapsedSeconds } from \"./working-status.ts\";\n\nexport interface ToolProgressDetails {\n\treadonly activity?: string;\n\treadonly startedAt: number;\n\treadonly maxWaitMs?: number;\n}\n\nexport function readToolProgress(details: unknown): ToolProgressDetails | undefined {\n\tif (typeof details !== \"object\" || details === null || !(\"progress\" in details)) return undefined;\n\tconst progress = details.progress;\n\tif (typeof progress !== \"object\" || progress === null || !(\"startedAt\" in progress)) return undefined;\n\tif (typeof progress.startedAt !== \"number\" || !Number.isFinite(progress.startedAt)) return undefined;\n\tif (\"activity\" in progress && typeof progress.activity !== \"string\") return undefined;\n\tif (\"maxWaitMs\" in progress && (typeof progress.maxWaitMs !== \"number\" || !Number.isFinite(progress.maxWaitMs))) {\n\t\treturn undefined;\n\t}\n\tconst activity = \"activity\" in progress && typeof progress.activity === \"string\" ? progress.activity : undefined;\n\tconst maxWaitMs = \"maxWaitMs\" in progress && typeof progress.maxWaitMs === \"number\" ? progress.maxWaitMs : undefined;\n\treturn { activity, startedAt: progress.startedAt, maxWaitMs };\n}\n\nexport function formatToolProgressLine(progress: ToolProgressDetails, now: number, spinnerFrame?: number): string {\n\tconst activity = progress.activity || \"working\";\n\tconst elapsed = formatWorkingElapsedSeconds((now - progress.startedAt) / 1_000);\n\tconst maxWait =\n\t\tprogress.maxWaitMs === undefined ? \"\" : ` / max ${Math.max(0, Math.floor(progress.maxWaitMs / 1_000))}s`;\n\tconst spinner = spinnerFrame === undefined ? \"⏵\" : [\"⏵\", \"⏷\", \"⏴\", \"⏶\"][spinnerFrame % 4];\n\treturn `${spinner} ${activity} · ${elapsed}${maxWait}`;\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import type { ToolExecutionComponent } from "./components/tool-execution.ts";
2
+ import type { ToolExecutionResult } from "./components/tool-execution-types.ts";
3
+ type ToolResultRevealComponent = Pick<ToolExecutionComponent, "updateResult">;
4
+ export type ToolResultRevealControllerOptions = {
5
+ readonly getSmoothStreaming: () => boolean;
6
+ readonly getSmoothStreamingFps: () => number;
7
+ readonly requestRender: () => void;
8
+ };
9
+ export declare class ToolResultRevealController {
10
+ #private;
11
+ constructor(options: ToolResultRevealControllerOptions);
12
+ update(id: string, component: ToolResultRevealComponent, partialResult: ToolExecutionResult): boolean;
13
+ finish(id: string): void;
14
+ refresh(): void;
15
+ stop(): void;
16
+ }
17
+ export {};
18
+ //# sourceMappingURL=tool-result-reveal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result-reveal.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/tool-result-reveal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAGhF,KAAK,yBAAyB,GAAG,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;AAW9E,MAAM,MAAM,iCAAiC,GAAG;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAC3C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,MAAM,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAiBF,qBAAa,0BAA0B;;IAQtC,YAAY,OAAO,EAAE,iCAAiC,EAIrD;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,yBAAyB,EAAE,aAAa,EAAE,mBAAmB,GAAG,OAAO,CA+BpG;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAMvB;IAED,OAAO,IAAI,IAAI,CAUd;IAED,IAAI,IAAI,IAAI,CAGX;CA0ED"}
@@ -0,0 +1,155 @@
1
+ import { BlockUnitCounter, DEFAULT_SMOOTH_FPS, MAX_SMOOTH_FPS, MIN_SMOOTH_FPS, nextStep } from "./streaming-reveal.js";
2
+ function firstText(result) {
3
+ const block = result.content[0];
4
+ return block?.type === "text" ? block.text : undefined;
5
+ }
6
+ function displayResult(state, revealed = state.revealed) {
7
+ const first = state.target.content[0];
8
+ if (first?.type !== "text")
9
+ throw new Error("Expected a text tool result");
10
+ return {
11
+ content: [{ ...first, text: state.unitCounter.slice(0, state.text, revealed) }, ...state.target.content.slice(1)],
12
+ details: state.target.details,
13
+ isError: false,
14
+ };
15
+ }
16
+ export class ToolResultRevealController {
17
+ #getSmoothStreaming;
18
+ #getSmoothStreamingFps;
19
+ #requestRender;
20
+ #states = new Map();
21
+ #timer;
22
+ #timerFps;
23
+ constructor(options) {
24
+ this.#getSmoothStreaming = options.getSmoothStreaming;
25
+ this.#getSmoothStreamingFps = options.getSmoothStreamingFps;
26
+ this.#requestRender = options.requestRender;
27
+ }
28
+ update(id, component, partialResult) {
29
+ const text = firstText(partialResult);
30
+ if (!this.#getSmoothStreaming() || text === undefined) {
31
+ this.#discard(id);
32
+ return false;
33
+ }
34
+ const existing = this.#states.get(id);
35
+ if (existing === undefined || existing.component !== component || !text.startsWith(existing.text)) {
36
+ const state = {
37
+ component,
38
+ target: partialResult,
39
+ text,
40
+ revealed: 0,
41
+ lastTickAt: performance.now(),
42
+ unitCounter: new BlockUnitCounter(),
43
+ };
44
+ state.revealed = state.unitCounter.count(0, text);
45
+ component.updateResult(displayResult(state), true);
46
+ this.#states.set(id, state);
47
+ this.#syncTimer();
48
+ return true;
49
+ }
50
+ const wasCaughtUp = existing.revealed >= existing.unitCounter.count(0, existing.text);
51
+ existing.target = partialResult;
52
+ existing.text = text;
53
+ const total = existing.unitCounter.count(0, text);
54
+ if (wasCaughtUp && existing.revealed < total)
55
+ existing.lastTickAt = performance.now();
56
+ this.#syncTimer();
57
+ return true;
58
+ }
59
+ finish(id) {
60
+ const state = this.#states.get(id);
61
+ if (state === undefined)
62
+ return;
63
+ state.component.updateResult(displayResult(state, state.unitCounter.count(0, state.text)), true);
64
+ this.#states.delete(id);
65
+ this.#syncTimer();
66
+ }
67
+ refresh() {
68
+ if (!this.#getSmoothStreaming()) {
69
+ this.#flushAll();
70
+ return;
71
+ }
72
+ if (!this.#hasBacklog()) {
73
+ this.#stopTimer();
74
+ return;
75
+ }
76
+ this.#startTimer(true);
77
+ }
78
+ stop() {
79
+ this.#stopTimer();
80
+ this.#states.clear();
81
+ }
82
+ #discard(id) {
83
+ if (!this.#states.delete(id))
84
+ return;
85
+ this.#syncTimer();
86
+ }
87
+ #flushAll() {
88
+ for (const state of this.#states.values()) {
89
+ state.component.updateResult(displayResult(state, state.unitCounter.count(0, state.text)), true);
90
+ }
91
+ this.#states.clear();
92
+ this.#stopTimer();
93
+ }
94
+ #hasBacklog() {
95
+ for (const state of this.#states.values()) {
96
+ if (state.revealed < state.unitCounter.count(0, state.text))
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+ #syncTimer() {
102
+ if (!this.#hasBacklog()) {
103
+ this.#stopTimer();
104
+ return;
105
+ }
106
+ this.#startTimer(false);
107
+ }
108
+ #startTimer(restart) {
109
+ const configuredFps = this.#getSmoothStreamingFps();
110
+ const fps = Number.isFinite(configuredFps)
111
+ ? Math.min(MAX_SMOOTH_FPS, Math.max(MIN_SMOOTH_FPS, configuredFps))
112
+ : DEFAULT_SMOOTH_FPS;
113
+ if (!restart && this.#timer !== undefined && this.#timerFps === fps)
114
+ return;
115
+ this.#stopTimer();
116
+ const now = performance.now();
117
+ for (const state of this.#states.values()) {
118
+ if (state.revealed < state.unitCounter.count(0, state.text))
119
+ state.lastTickAt = now;
120
+ }
121
+ const timer = setInterval(() => this.#tick(), 1000 / fps);
122
+ timer.unref();
123
+ this.#timer = timer;
124
+ this.#timerFps = fps;
125
+ }
126
+ #stopTimer() {
127
+ if (this.#timer !== undefined)
128
+ clearInterval(this.#timer);
129
+ this.#timer = undefined;
130
+ this.#timerFps = undefined;
131
+ }
132
+ #tick() {
133
+ if (!this.#getSmoothStreaming()) {
134
+ this.#flushAll();
135
+ this.#requestRender();
136
+ return;
137
+ }
138
+ const now = performance.now();
139
+ let rendered = false;
140
+ for (const state of this.#states.values()) {
141
+ const total = state.unitCounter.count(0, state.text);
142
+ const backlog = total - state.revealed;
143
+ if (backlog <= 0)
144
+ continue;
145
+ state.revealed = Math.min(total, state.revealed + nextStep(backlog, now - state.lastTickAt));
146
+ state.lastTickAt = now;
147
+ state.component.updateResult(displayResult(state), true);
148
+ rendered = true;
149
+ }
150
+ if (rendered)
151
+ this.#requestRender();
152
+ this.#syncTimer();
153
+ }
154
+ }
155
+ //# sourceMappingURL=tool-result-reveal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result-reveal.js","sourceRoot":"","sources":["../../../src/modes/interactive/tool-result-reveal.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ToolExecutionComponent } from \"./components/tool-execution.ts\";\nimport type { ToolExecutionResult } from \"./components/tool-execution-types.ts\";\nimport { BlockUnitCounter, DEFAULT_SMOOTH_FPS, MAX_SMOOTH_FPS, MIN_SMOOTH_FPS, nextStep } from \"./streaming-reveal.ts\";\n\ntype ToolResultRevealComponent = Pick<ToolExecutionComponent, \"updateResult\">;\n\ntype ToolResultRevealState = {\n\tcomponent: ToolResultRevealComponent;\n\ttarget: ToolExecutionResult;\n\ttext: string;\n\trevealed: number;\n\tlastTickAt: number;\n\tunitCounter: BlockUnitCounter;\n};\n\nexport type ToolResultRevealControllerOptions = {\n\treadonly getSmoothStreaming: () => boolean;\n\treadonly getSmoothStreamingFps: () => number;\n\treadonly requestRender: () => void;\n};\n\nfunction firstText(result: ToolExecutionResult): string | undefined {\n\tconst block = result.content[0];\n\treturn block?.type === \"text\" ? block.text : undefined;\n}\n\nfunction displayResult(state: ToolResultRevealState, revealed = state.revealed): ToolExecutionResult {\n\tconst first = state.target.content[0];\n\tif (first?.type !== \"text\") throw new Error(\"Expected a text tool result\");\n\treturn {\n\t\tcontent: [{ ...first, text: state.unitCounter.slice(0, state.text, revealed) }, ...state.target.content.slice(1)],\n\t\tdetails: state.target.details,\n\t\tisError: false,\n\t};\n}\n\nexport class ToolResultRevealController {\n\treadonly #getSmoothStreaming: () => boolean;\n\treadonly #getSmoothStreamingFps: () => number;\n\treadonly #requestRender: () => void;\n\treadonly #states = new Map<string, ToolResultRevealState>();\n\t#timer: NodeJS.Timeout | undefined;\n\t#timerFps: number | undefined;\n\n\tconstructor(options: ToolResultRevealControllerOptions) {\n\t\tthis.#getSmoothStreaming = options.getSmoothStreaming;\n\t\tthis.#getSmoothStreamingFps = options.getSmoothStreamingFps;\n\t\tthis.#requestRender = options.requestRender;\n\t}\n\n\tupdate(id: string, component: ToolResultRevealComponent, partialResult: ToolExecutionResult): boolean {\n\t\tconst text = firstText(partialResult);\n\t\tif (!this.#getSmoothStreaming() || text === undefined) {\n\t\t\tthis.#discard(id);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst existing = this.#states.get(id);\n\t\tif (existing === undefined || existing.component !== component || !text.startsWith(existing.text)) {\n\t\t\tconst state: ToolResultRevealState = {\n\t\t\t\tcomponent,\n\t\t\t\ttarget: partialResult,\n\t\t\t\ttext,\n\t\t\t\trevealed: 0,\n\t\t\t\tlastTickAt: performance.now(),\n\t\t\t\tunitCounter: new BlockUnitCounter(),\n\t\t\t};\n\t\t\tstate.revealed = state.unitCounter.count(0, text);\n\t\t\tcomponent.updateResult(displayResult(state), true);\n\t\t\tthis.#states.set(id, state);\n\t\t\tthis.#syncTimer();\n\t\t\treturn true;\n\t\t}\n\n\t\tconst wasCaughtUp = existing.revealed >= existing.unitCounter.count(0, existing.text);\n\t\texisting.target = partialResult;\n\t\texisting.text = text;\n\t\tconst total = existing.unitCounter.count(0, text);\n\t\tif (wasCaughtUp && existing.revealed < total) existing.lastTickAt = performance.now();\n\t\tthis.#syncTimer();\n\t\treturn true;\n\t}\n\n\tfinish(id: string): void {\n\t\tconst state = this.#states.get(id);\n\t\tif (state === undefined) return;\n\t\tstate.component.updateResult(displayResult(state, state.unitCounter.count(0, state.text)), true);\n\t\tthis.#states.delete(id);\n\t\tthis.#syncTimer();\n\t}\n\n\trefresh(): void {\n\t\tif (!this.#getSmoothStreaming()) {\n\t\t\tthis.#flushAll();\n\t\t\treturn;\n\t\t}\n\t\tif (!this.#hasBacklog()) {\n\t\t\tthis.#stopTimer();\n\t\t\treturn;\n\t\t}\n\t\tthis.#startTimer(true);\n\t}\n\n\tstop(): void {\n\t\tthis.#stopTimer();\n\t\tthis.#states.clear();\n\t}\n\n\t#discard(id: string): void {\n\t\tif (!this.#states.delete(id)) return;\n\t\tthis.#syncTimer();\n\t}\n\n\t#flushAll(): void {\n\t\tfor (const state of this.#states.values()) {\n\t\t\tstate.component.updateResult(displayResult(state, state.unitCounter.count(0, state.text)), true);\n\t\t}\n\t\tthis.#states.clear();\n\t\tthis.#stopTimer();\n\t}\n\n\t#hasBacklog(): boolean {\n\t\tfor (const state of this.#states.values()) {\n\t\t\tif (state.revealed < state.unitCounter.count(0, state.text)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t#syncTimer(): void {\n\t\tif (!this.#hasBacklog()) {\n\t\t\tthis.#stopTimer();\n\t\t\treturn;\n\t\t}\n\t\tthis.#startTimer(false);\n\t}\n\n\t#startTimer(restart: boolean): void {\n\t\tconst configuredFps = this.#getSmoothStreamingFps();\n\t\tconst fps = Number.isFinite(configuredFps)\n\t\t\t? Math.min(MAX_SMOOTH_FPS, Math.max(MIN_SMOOTH_FPS, configuredFps))\n\t\t\t: DEFAULT_SMOOTH_FPS;\n\t\tif (!restart && this.#timer !== undefined && this.#timerFps === fps) return;\n\t\tthis.#stopTimer();\n\t\tconst now = performance.now();\n\t\tfor (const state of this.#states.values()) {\n\t\t\tif (state.revealed < state.unitCounter.count(0, state.text)) state.lastTickAt = now;\n\t\t}\n\t\tconst timer = setInterval(() => this.#tick(), 1000 / fps);\n\t\ttimer.unref();\n\t\tthis.#timer = timer;\n\t\tthis.#timerFps = fps;\n\t}\n\n\t#stopTimer(): void {\n\t\tif (this.#timer !== undefined) clearInterval(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#timerFps = undefined;\n\t}\n\n\t#tick(): void {\n\t\tif (!this.#getSmoothStreaming()) {\n\t\t\tthis.#flushAll();\n\t\t\tthis.#requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = performance.now();\n\t\tlet rendered = false;\n\t\tfor (const state of this.#states.values()) {\n\t\t\tconst total = state.unitCounter.count(0, state.text);\n\t\t\tconst backlog = total - state.revealed;\n\t\t\tif (backlog <= 0) continue;\n\t\t\tstate.revealed = Math.min(total, state.revealed + nextStep(backlog, now - state.lastTickAt));\n\t\t\tstate.lastTickAt = now;\n\t\t\tstate.component.updateResult(displayResult(state), true);\n\t\t\trendered = true;\n\t\t}\n\t\tif (rendered) this.#requestRender();\n\t\tthis.#syncTimer();\n\t}\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { type FSWatcher, type WatchListener } from "node:fs";
1
+ import { type FSWatcher, type WatchListener, type WatchOptions } from "node:fs";
2
2
  export declare const FS_WATCH_RETRY_DELAY_MS = 5000;
3
3
  export declare function closeWatcher(watcher: FSWatcher | null | undefined): void;
4
- export declare function watchWithErrorHandler(path: string, listener: WatchListener<string>, onError: () => void): FSWatcher | null;
4
+ export declare function watchWithErrorHandler(path: string, listener: WatchListener<string>, onError: () => void, options?: WatchOptions): FSWatcher | null;
5
5
  //# sourceMappingURL=fs-watch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fs-watch.d.ts","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAS,MAAM,SAAS,CAAC;AAEpE,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAUxE;AAED,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,OAAO,EAAE,MAAM,IAAI,GACjB,SAAS,GAAG,IAAI,CASlB"}
1
+ {"version":3,"file":"fs-watch.d.ts","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAS,MAAM,SAAS,CAAC;AAEvF,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAUxE;AAED,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,CAAC,EAAE,YAAY,GACpB,SAAS,GAAG,IAAI,CASlB"}
@@ -11,9 +11,9 @@ export function closeWatcher(watcher) {
11
11
  // Ignore watcher close errors
12
12
  }
13
13
  }
14
- export function watchWithErrorHandler(path, listener, onError) {
14
+ export function watchWithErrorHandler(path, listener, onError, options) {
15
15
  try {
16
- const watcher = watch(path, listener);
16
+ const watcher = watch(path, { ...options, encoding: "utf8" }, listener);
17
17
  watcher.on("error", onError);
18
18
  return watcher;
19
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fs-watch.js","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,EAAE,MAAM,SAAS,CAAC;AAEpE,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,OAAqC;IACjE,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO;IACR,CAAC;IAED,IAAI,CAAC;QACJ,OAAO,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACR,8BAA8B;IAC/B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,IAAY,EACZ,QAA+B,EAC/B,OAAmB;IAEnB,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACtC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC","sourcesContent":["import { type FSWatcher, type WatchListener, watch } from \"node:fs\";\n\nexport const FS_WATCH_RETRY_DELAY_MS = 5000;\n\nexport function closeWatcher(watcher: FSWatcher | null | undefined): void {\n\tif (!watcher) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\twatcher.close();\n\t} catch {\n\t\t// Ignore watcher close errors\n\t}\n}\n\nexport function watchWithErrorHandler(\n\tpath: string,\n\tlistener: WatchListener<string>,\n\tonError: () => void,\n): FSWatcher | null {\n\ttry {\n\t\tconst watcher = watch(path, listener);\n\t\twatcher.on(\"error\", onError);\n\t\treturn watcher;\n\t} catch {\n\t\tonError();\n\t\treturn null;\n\t}\n}\n"]}
1
+ {"version":3,"file":"fs-watch.js","sourceRoot":"","sources":["../../src/utils/fs-watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyD,KAAK,EAAE,MAAM,SAAS,CAAC;AAEvF,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,OAAqC;IACjE,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO;IACR,CAAC;IAED,IAAI,CAAC;QACJ,OAAO,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACR,8BAA8B;IAC/B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,IAAY,EACZ,QAA+B,EAC/B,OAAmB,EACnB,OAAsB;IAEtB,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;QACxE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC","sourcesContent":["import { type FSWatcher, type WatchListener, type WatchOptions, watch } from \"node:fs\";\n\nexport const FS_WATCH_RETRY_DELAY_MS = 5000;\n\nexport function closeWatcher(watcher: FSWatcher | null | undefined): void {\n\tif (!watcher) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\twatcher.close();\n\t} catch {\n\t\t// Ignore watcher close errors\n\t}\n}\n\nexport function watchWithErrorHandler(\n\tpath: string,\n\tlistener: WatchListener<string>,\n\tonError: () => void,\n\toptions?: WatchOptions,\n): FSWatcher | null {\n\ttry {\n\t\tconst watcher = watch(path, { ...options, encoding: \"utf8\" }, listener);\n\t\twatcher.on(\"error\", onError);\n\t\treturn watcher;\n\t} catch {\n\t\tonError();\n\t\treturn null;\n\t}\n}\n"]}
@@ -12,6 +12,7 @@ Extensions are TypeScript modules that extend pi's behavior. They can subscribe
12
12
  - **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
13
13
  - **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
14
14
  - **Custom commands** - Register commands like `/mycommand` via `pi.registerCommand()`
15
+ - **Model fallback** - The bundled [`/fallback`](#bundled-fallback-command) command manages global per-model retry chains. Use `/fallback <target> <fallback1> [fallback2 ...]` for scripts, or `/fallback` in the TUI to view and edit chains. `--no-model-fallback` and `SENPI_NO_FALLBACK=1` disable it for one run.
15
16
  - **Session persistence** - Store state that survives restarts via `pi.appendEntry()`
16
17
  - **Custom rendering** - Control how tool calls/results and messages appear in TUI
17
18
 
@@ -31,6 +32,7 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
31
32
  ## Table of Contents
32
33
 
33
34
  - [Quick Start](#quick-start)
35
+ - [Bundled /fallback Command](#bundled-fallback-command)
34
36
  - [Extension Locations](#extension-locations)
35
37
  - [Available Imports](#available-imports)
36
38
  - [Writing an Extension](#writing-an-extension)
@@ -45,6 +47,7 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
45
47
  - [ExtensionContext](#extensioncontext)
46
48
  - [ExtensionCommandContext](#extensioncommandcontext)
47
49
  - [ExtensionAPI Methods](#extensionapi-methods)
50
+ - [Config reload](#config-reload)
48
51
  - [State Management](#state-management)
49
52
  - [Custom Tools](#custom-tools)
50
53
  - [Dynamic Tool Loading](#dynamic-tool-loading)
@@ -106,6 +109,32 @@ Test with `--extension` (or `-e`) flag:
106
109
  pi -e ./my-extension.ts
107
110
  ```
108
111
 
112
+ ## Bundled /fallback Command
113
+
114
+ The bundled `model-fallback` extension registers `/fallback` to manage global per-model retry fallback chains. The command writes through the active session settings manager, so a saved chain is available to the current session's next retry without restarting Senpi. See [Settings](settings.md#model-fallback-chains) for selector syntax and runtime behavior.
115
+
116
+ Use the quick-set form in scripts or any non-TUI mode:
117
+
118
+ ```text
119
+ /fallback anthropic/claude-fable-5 ccapi/kimi-k3:max
120
+ ```
121
+
122
+ The first argument is the exact primary model selector; each later argument is an ordered fallback selector. Quick-set validates selectors before saving. At least one fallback is required:
123
+
124
+ ```text
125
+ /fallback <target> <fallback1> [fallback2 ...]
126
+ ```
127
+
128
+ Run `/fallback` without arguments in the TUI for a menu that can:
129
+
130
+ - show configured chains and live fallback state;
131
+ - add or edit a chain by choosing the target, each fallback, and either an explicit or inherited thinking level;
132
+ - remove a chain;
133
+ - toggle `retry.modelFallback`; and
134
+ - choose `retry.fallbackRevertPolicy` (`cooldown-expiry` or `never`).
135
+
136
+ Use quick-set in non-TUI modes; the no-argument menu requires interactive UI. Pass `--no-model-fallback` or set `SENPI_NO_FALLBACK=1` to disable fallback for that process without modifying saved settings.
137
+
109
138
  ## Extension Locations
110
139
 
111
140
  > **Security:** Extensions run with your full system permissions and can execute arbitrary code. Only install from sources you trust.
@@ -1717,6 +1746,55 @@ pi.events.on("my:event", (data) => { ... });
1717
1746
  pi.events.emit("my:event", { ... });
1718
1747
  ```
1719
1748
 
1749
+ ## Config reload
1750
+
1751
+ Senpi's default-on `config-reload` builtin watches configured global surfaces and trusted project-local `.senpi` surfaces. A real content change requests the normal full session reload when the agent is idle; busy or compacting sessions defer it until a safe idle edge. Parseable built-in files (`settings.json`, `models.json`, and `keybindings.json`) are validated before reload, so a rejected edit keeps the running configuration active.
1752
+
1753
+ Configure it in `settings.json` with optional fields; omitted fields use the defaults shown here. Invalid `configReload` fields are ignored individually, so a malformed block falls back to these defaults rather than disabling watching:
1754
+
1755
+ ```json
1756
+ {
1757
+ "configReload": {
1758
+ "enabled": true,
1759
+ "debounceMs": 200,
1760
+ "watch": {
1761
+ "settings": true,
1762
+ "models": true,
1763
+ "keybindings": true,
1764
+ "prompts": true,
1765
+ "skills": true,
1766
+ "extensions": true
1767
+ }
1768
+ }
1769
+ }
1770
+ ```
1771
+
1772
+ `skills` covers skill paths declared in settings as well as trusted project skills. Themes, credentials, sessions, and reload logs are not watched. Project config surfaces require project trust.
1773
+
1774
+ ### `config-watch:*` event protocol
1775
+
1776
+ Extensions can register additional reloadable surfaces on the shared in-process `pi.events` bus. The channels are `config-watch:register`, `config-watch:unregister`, `config-watch:ready`, `config-watch:changed`, `config-watch:reloaded`, and `config-watch:rejected`. Registrations with the same `id` are last-wins. The registration wire shape is:
1777
+
1778
+ ```typescript
1779
+ {
1780
+ id: string;
1781
+ displayName: string;
1782
+ targets: Array<{
1783
+ path: string;
1784
+ kind: "file" | "dir";
1785
+ filterGlobs?: string[]; // simple name/suffix patterns, for example ["omo.json", "omo.jsonc"]
1786
+ }>;
1787
+ validate?: (changedPaths: readonly string[]) =>
1788
+ | { ok: true }
1789
+ | { ok: false; errors: string[] }
1790
+ | Promise<{ ok: true } | { ok: false; errors: string[] }>;
1791
+ }
1792
+ ```
1793
+
1794
+ `config-watch:changed` emits `{ registrationId, paths, deferred }`; `config-watch:reloaded` emits `{ registrationId, paths }`; and `config-watch:rejected` emits `{ registrationId, paths, errors }`. `config-watch:ready` emits `{ enabled }`. Treat all event payloads as untrusted at the receiver boundary.
1795
+
1796
+ In TUI mode the builtin requests the host reload action and shows notifications. RPC hosts without a reload action intentionally run in degraded mode: watchers remain active and `config-watch:changed` still emits, but no reload is requested. Print mode starts no watchers so a short-lived process can exit normally.
1797
+
1720
1798
  ### pi.registerProvider(name, config)
1721
1799
 
1722
1800
  Register or override a model provider dynamically. Useful for proxies, custom endpoints, or team-wide model configurations.
@@ -2898,7 +2976,7 @@ const highlighted = highlightCode(code, lang, theme);
2898
2976
  | Interactive | `"tui"` | `true` | Full TUI with terminal rendering |
2899
2977
  | RPC (`--mode rpc`) | `"rpc"` | `true` | Dialogs and notifications via JSON protocol; `custom()` returns `undefined`. See [rpc.md](rpc.md) |
2900
2978
  | JSON (`--mode json`) | `"json"` | `false` | Event stream to stdout; UI methods are no-ops |
2901
- | Print (`-p`) | `"print"` | `false` | Extensions run but can't prompt |
2979
+ | Print (`-p`) | `"print"` | `false` | Extensions run but can't prompt; `config-reload` deliberately starts no filesystem watchers |
2902
2980
 
2903
2981
  Use `ctx.mode === "tui"` before TUI-specific features (`custom()`, component factories, terminal input). Use `ctx.hasUI` before dialog and notification methods that work in both TUI and RPC modes.
2904
2982
 
package/docs/mcp.md CHANGED
@@ -87,6 +87,15 @@ Per-request timeout. Default `30000`.
87
87
  ### `connectTimeoutMs`
88
88
  Connect + initialize handshake timeout. Default `15000`.
89
89
 
90
+ ### `startupTimeoutMs`
91
+ Bounded startup window (ms) that a server's first connect + catalog fetch is
92
+ raced against during session attach. A server that does not settle inside the
93
+ window keeps connecting in the background and surfaces its tools when ready, so
94
+ a slow or wedged server never blocks the first turn. Default `250`. Set higher
95
+ to wait for tools before the first turn, `0` to never wait. The
96
+ `SENPI_MCP_STARTUP_TIMEOUT_MS` environment variable overrides this for every
97
+ server.
98
+
90
99
  ### `includeTools`
91
100
  Glob allowlist (`*` wildcards) over server-side tool names. Default: all.
92
101
 
@@ -199,7 +208,7 @@ Extension-declared servers use bare names (no prefix).
199
208
  | `degraded` | transient failure; auto-reconnect with backoff is running | wait, or `/mcp reconnect <name>` |
200
209
  | tools missing | server filtered/disabled, or hidden behind search | check `includeTools`/`excludeTools`, ask the model to `tool_search` |
201
210
  | child exits at spawn (EOF) | bad `command`/`args`/`env` | `/mcp logs <name>` shows the captured stderr |
202
- | slow first call | lazy server cold boot | use `lifecycle:"eager"` or `"keep-alive"` |
211
+ | slow first call | lazy server cold boot (tools attach in the background) | raise `startupTimeoutMs`, or use `lifecycle:"eager"` / `"keep-alive"` |
203
212
 
204
213
  ## Security notes
205
214
 
package/docs/models.md CHANGED
@@ -209,6 +209,7 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
209
209
  | `contextWindow` | No | `128000` | Context window size in tokens |
210
210
  | `maxTokens` | No | `16384` | Maximum output tokens |
211
211
  | `cost` | No | all zeros | Per-million-token rates with optional request-wide input pricing tiers |
212
+ | `recoverTextToolCalls` | No | Claude ID default | Enables or disables recovery of leaked Claude XML tool calls from streamed assistant text. This is a top-level model field, not a `compat` field. |
212
213
  | `compat` | No | provider `compat` | Provider compatibility overrides. Merged with provider-level `compat` when both are set. |
213
214
 
214
215
  A cost tier supplies a complete alternate rate set and applies to the full request when total input usage (`input + cacheRead + cacheWrite`) exceeds `inputTokensAbove`. When multiple tiers match, the highest threshold wins.
@@ -237,6 +238,106 @@ Current behavior:
237
238
  - `/model`, `--list-models`, and the interactive footer display entries by model `id`.
238
239
  - The configured `name` is used for model matching and secondary model detail text. It does not replace the footer/status-bar model id.
239
240
 
241
+ ## Claude text tool-call recovery
242
+
243
+ Some Claude-family models or compatible gateways may accidentally emit a
244
+ supported tool call as XML assistant text instead of a native provider tool
245
+ call. Senpi can recover these calls while the response is streaming, execute a
246
+ complete validated call through the normal agent loop, and persist it as native
247
+ tool-call history for the next request.
248
+
249
+ The setting is the top-level boolean `recoverTextToolCalls`. Non-boolean values
250
+ in `models.json` are rejected.
251
+
252
+ ### Recovery activation precedence
253
+
254
+ | Priority | Condition | Result |
255
+ |----------|-----------|--------|
256
+ | 1 | The model has a configured text tool-call protocol | Disabled |
257
+ | 2 | `recoverTextToolCalls` is explicitly `true` or `false` | Configured boolean |
258
+ | 3 | The original selected model id matches the Claude boundary rule | Claude ID default |
259
+
260
+ The default boundary rule is
261
+ `/(^|[^a-z0-9])claude([^a-z0-9]|$)/i`. It is evaluated against the
262
+ original selected model id, before a provider alias or request hook rewrites
263
+ the wire model id. Recovery is provider/API independent and is skipped when
264
+ the request has no tools.
265
+
266
+ Explicitly disable recovery for a Claude model:
267
+
268
+ ```json
269
+ {
270
+ "providers": {
271
+ "anthropic": {
272
+ "modelOverrides": {
273
+ "anthropic/claude-sonnet-4": {
274
+ "recoverTextToolCalls": false
275
+ }
276
+ }
277
+ }
278
+ }
279
+ }
280
+ ```
281
+
282
+ Explicitly enable recovery for a non-Claude custom alias:
283
+
284
+ ```json
285
+ {
286
+ "providers": {
287
+ "custom-proxy": {
288
+ "baseUrl": "https://proxy.example.com/v1",
289
+ "apiKey": "$CUSTOM_PROXY_KEY",
290
+ "api": "openai-completions",
291
+ "models": [
292
+ {
293
+ "id": "model-without-claude-name",
294
+ "recoverTextToolCalls": true
295
+ }
296
+ ]
297
+ }
298
+ }
299
+ }
300
+ ```
301
+
302
+ Text tool-call protocols retain exclusive ownership of text parsing. If
303
+ `toolCallFormat` resolves for a model, recovery stays disabled even when
304
+ `recoverTextToolCalls` is explicitly `true`.
305
+
306
+ ### Recovery scan boundaries
307
+
308
+ | Content | Scanned | Behavior |
309
+ |---------|---------|----------|
310
+ | Ordinary assistant text | Yes | Supported leaked invokes may become native tool calls |
311
+ | Inline and fenced code | No | Preserved byte-for-byte as text |
312
+ | Thinking and redacted thinking | No | Preserved with signatures and metadata |
313
+ | Provider-native and native tool-call blocks | No | Passed through without XML inspection |
314
+ | Historical persisted messages | No | Replayed as stored; legacy XML text is not rewritten |
315
+
316
+ The recognized syntax is limited to bare `invoke`/`parameter`/`function_calls`
317
+ tags and their exact lowercase `antml:` variants. Unknown tools, ambiguous tool
318
+ names, unrelated tags, and arbitrary namespace prefixes remain text.
319
+
320
+ ### Recovery outcomes
321
+
322
+ | Condition | Outcome |
323
+ |-----------|---------|
324
+ | Complete valid call | Execute once |
325
+ | Incomplete or invalid call | Do not execute |
326
+ | Caller abort | Interrupted; do not execute |
327
+ | Late native ID collision | Fail closed; do not execute |
328
+
329
+ An incomplete call is persisted as an incomplete native tool call and produces
330
+ a safe error result asking the model to re-issue complete arguments. A caller
331
+ abort always remains interrupted, even if a complete leaked call was already
332
+ recognized. Native/recovered ID collisions discovered after an event was
333
+ emitted terminate with an error instead of renaming provider IDs.
334
+
335
+ Recovered calls are persisted and replayed in each provider's native format.
336
+ Senpi does not migrate or rewrite older stored assistant text. The next
337
+ prompt-cache suffix may change after recovery because new messages contain
338
+ native tool-call history instead of leaked XML; that cache-key change is
339
+ expected.
340
+
240
341
  ### Thinking Level Map
241
342
 
242
343
  Use `thinkingLevelMap` on a model to describe model-specific thinking controls. Keys are senpi thinking levels: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Maps may contain holes; for example, a model can expose `high` and `max` without exposing `xhigh`.
@@ -340,7 +441,7 @@ Use `modelOverrides` to customize built-in models and matching extension-registe
340
441
  }
341
442
  ```
342
443
 
343
- `modelOverrides` supports these fields per model: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `headers`, `compat`.
444
+ `modelOverrides` supports these fields per model: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `headers`, `recoverTextToolCalls`, `compat`.
344
445
 
345
446
  Direct OpenAI GPT-5.6 Sol, Terra, and Luna default to a `272000` context window so requests remain within OpenAI's short-context pricing tier. To opt into OpenAI's 1.05M context window, increase it for each model you use:
346
447
 
package/docs/providers.md CHANGED
@@ -91,6 +91,7 @@ pi
91
91
  | Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | `xiaomi-token-plan-cn` |
92
92
  | Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` | `xiaomi-token-plan-ams` |
93
93
  | Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` | `xiaomi-token-plan-sgp` |
94
+ | Alibaba Token Plan (ap-southeast-1) | `ALIBABA_TOKEN_PLAN_API_KEY` | `alibaba-token-plan` |
94
95
 
95
96
  Reference for environment variables and `auth.json` keys: [`const envMap`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/env-api-keys.ts) in [`packages/ai/src/env-api-keys.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/env-api-keys.ts).
96
97
 
@@ -112,7 +113,8 @@ Store credentials in `~/.pi/agent/auth.json`:
112
113
  "xiaomi": { "type": "api_key", "key": "..." },
113
114
  "xiaomi-token-plan-cn": { "type": "api_key", "key": "..." },
114
115
  "xiaomi-token-plan-ams": { "type": "api_key", "key": "..." },
115
- "xiaomi-token-plan-sgp": { "type": "api_key", "key": "..." }
116
+ "xiaomi-token-plan-sgp": { "type": "api_key", "key": "..." },
117
+ "alibaba-token-plan": { "type": "api_key", "key": "..." }
116
118
  }
117
119
  ```
118
120
 
package/docs/settings.md CHANGED
@@ -206,6 +206,9 @@ Set `PI_SKIP_VERSION_CHECK=1` to disable the senpi version update check. Use `--
206
206
  | `retry.enabled` | boolean | `true` | Enable automatic agent-level retry on transient errors |
207
207
  | `retry.maxRetries` | number | `3` | Maximum agent-level retry attempts |
208
208
  | `retry.baseDelayMs` | number | `2000` | Base delay for agent-level exponential backoff (2s, 4s, 8s) |
209
+ | `retry.modelFallback` | boolean | `true` | Let eligible retry failures advance through configured per-model fallback chains |
210
+ | `retry.fallbackChains` | `Record<string, string[]>` | `{}` | Ordered exact model-selector to fallback-selector chains |
211
+ | `retry.fallbackRevertPolicy` | `"cooldown-expiry"` \| `"never"` | `"cooldown-expiry"` | Automatic primary-model restoration policy |
209
212
  | `retry.provider.timeoutMs` | number | `300000` | Provider/SDK request timeout and stream idle timeout in milliseconds |
210
213
  | `retry.provider.maxRetries` | number | `0` | Provider/SDK retry attempts |
211
214
  | `retry.provider.maxRetryDelayMs` | number | `60000` | Max server-requested delay before failing (60s) |
@@ -229,6 +232,38 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic
229
232
  }
230
233
  ```
231
234
 
235
+ #### Model fallback chains
236
+
237
+ `retry.fallbackChains` maps one exact primary-model selector to an ordered list of fallback selectors. A selector is `provider/model` with an optional `:thinking-level` suffix. For example, this switches Fable 5 to Kimi K3 at `max` thinking when an eligible failure occurs:
238
+
239
+ ```json
240
+ {
241
+ "retry": {
242
+ "modelFallback": true,
243
+ "fallbackChains": {
244
+ "anthropic/claude-fable-5": ["ccapi/kimi-k3:max"]
245
+ },
246
+ "fallbackRevertPolicy": "cooldown-expiry"
247
+ }
248
+ }
249
+ ```
250
+
251
+ A chain is only for the exact primary model it names: selector lookup first considers an exact thinking-level selector, then the same `provider/model` without its thinking suffix. Wildcard selectors, role keys such as `default`, and other catch-all chains are not supported.
252
+
253
+ A fallback entry with `:thinking-level` requests that level on the target model; a bare entry inherits the current thinking level. Either value is clamped to the target model's supported levels. When an unpinned fallback later returns to the primary, it restores the original thinking level unless you changed it while using the fallback.
254
+
255
+ `/fallback` writes these settings to the global settings file. Project settings are still merged when read; because `fallbackChains` is a nested map, a project `retry.fallbackChains` replaces the global map rather than merging individual chain keys.
256
+
257
+ #### Fallback behavior and diagnostics
258
+
259
+ With `retry.enabled` and `retry.modelFallback` enabled, Senpi can switch from a transient or eligible hard provider failure to the next configured candidate. The switch continues the current turn without changing the existing conversation prefix, preserving prompt-cache inputs; fallback lifecycle events are never added to model context. Returning to a primary model happens only at a turn boundary, never while a response is streaming.
260
+
261
+ Anthropic streaming refusals are identified from typed `stopDetails`. A configured candidate receives an immediate **pinned** fallback switch with a user-visible fallback notice: Senpi does not retry the refusing model and a pinned fallback never auto-reverts. Set `retry.fallbackRevertPolicy` to `"cooldown-expiry"` (the default) to return an unpinned fallback to its primary after the primary's cooldown expires, or `"never"` to keep the fallback until you change models.
262
+
263
+ Fallback decisions are process-local. A `senpi-task` or subagent child process reads its own settings and maintains its own in-memory suppression state; it does not affect its parent process. Disable fallback for one run without changing settings with `--no-model-fallback` or `SENPI_NO_FALLBACK=1`.
264
+
265
+ For diagnostics, Senpi writes sanitized NDJSON records for candidate skips, cooldowns, switches, reverts, manual clears, and validation warnings to `<agentDir>/logs/fallback.log`. The file is mode `0600` and rotates at 5 MB (`fallback.log.1`).
266
+
232
267
  ### Message Delivery
233
268
 
234
269
  | Setting | Type | Default | Description |