@qduc/term2 0.6.0 → 0.7.0

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 (546) hide show
  1. package/dist/agent.d.ts +9 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +139 -68
  4. package/dist/agent.js.map +1 -1
  5. package/dist/agent.test.js +179 -16
  6. package/dist/agent.test.js.map +1 -1
  7. package/dist/app.d.ts +16 -3
  8. package/dist/app.d.ts.map +1 -1
  9. package/dist/app.js +186 -23
  10. package/dist/app.js.map +1 -1
  11. package/dist/app.large-uncached-warning.test.d.ts +2 -0
  12. package/dist/app.large-uncached-warning.test.d.ts.map +1 -0
  13. package/dist/app.large-uncached-warning.test.js +15 -0
  14. package/dist/app.large-uncached-warning.test.js.map +1 -0
  15. package/dist/app.startup-banner.test.js +3 -19
  16. package/dist/app.startup-banner.test.js.map +1 -1
  17. package/dist/cli.js +170 -71
  18. package/dist/cli.js.map +1 -1
  19. package/dist/components/Banner.d.ts.map +1 -1
  20. package/dist/components/Banner.js +35 -17
  21. package/dist/components/Banner.js.map +1 -1
  22. package/dist/components/BottomArea.d.ts +7 -1
  23. package/dist/components/BottomArea.d.ts.map +1 -1
  24. package/dist/components/BottomArea.js +8 -6
  25. package/dist/components/BottomArea.js.map +1 -1
  26. package/dist/components/BottomArea.test.js +14 -1
  27. package/dist/components/BottomArea.test.js.map +1 -1
  28. package/dist/components/CommandMessage.d.ts.map +1 -1
  29. package/dist/components/CommandMessage.js +31 -1
  30. package/dist/components/CommandMessage.js.map +1 -1
  31. package/dist/components/CommandMessage.test.js +150 -31
  32. package/dist/components/CommandMessage.test.js.map +1 -1
  33. package/dist/components/Common/MenuContainer.d.ts +20 -0
  34. package/dist/components/Common/MenuContainer.d.ts.map +1 -0
  35. package/dist/components/Common/MenuContainer.js +52 -0
  36. package/dist/components/Common/MenuContainer.js.map +1 -0
  37. package/dist/components/Common/ScrollableTabBar.d.ts +26 -0
  38. package/dist/components/Common/ScrollableTabBar.d.ts.map +1 -0
  39. package/dist/components/Common/ScrollableTabBar.js +26 -0
  40. package/dist/components/Common/ScrollableTabBar.js.map +1 -0
  41. package/dist/components/Common/compute-visible-tabs.d.ts +16 -0
  42. package/dist/components/Common/compute-visible-tabs.d.ts.map +1 -0
  43. package/dist/components/Common/compute-visible-tabs.js +53 -0
  44. package/dist/components/Common/compute-visible-tabs.js.map +1 -0
  45. package/dist/components/Common/compute-visible-tabs.test.d.ts +2 -0
  46. package/dist/components/Common/compute-visible-tabs.test.d.ts.map +1 -0
  47. package/dist/components/Common/compute-visible-tabs.test.js +97 -0
  48. package/dist/components/Common/compute-visible-tabs.test.js.map +1 -0
  49. package/dist/components/HandoffConfirmationPrompt.d.ts +9 -0
  50. package/dist/components/HandoffConfirmationPrompt.d.ts.map +1 -0
  51. package/dist/components/HandoffConfirmationPrompt.js +38 -0
  52. package/dist/components/HandoffConfirmationPrompt.js.map +1 -0
  53. package/dist/components/HandoffConfirmationPrompt.test.d.ts +2 -0
  54. package/dist/components/HandoffConfirmationPrompt.test.d.ts.map +1 -0
  55. package/dist/components/HandoffConfirmationPrompt.test.js +12 -0
  56. package/dist/components/HandoffConfirmationPrompt.test.js.map +1 -0
  57. package/dist/components/Input/PopupManager.d.ts +5 -1
  58. package/dist/components/Input/PopupManager.d.ts.map +1 -1
  59. package/dist/components/Input/PopupManager.js +2 -2
  60. package/dist/components/Input/PopupManager.js.map +1 -1
  61. package/dist/components/Input/popup-key-navigation.js +2 -2
  62. package/dist/components/Input/popup-key-navigation.js.map +1 -1
  63. package/dist/components/Input/popup-key-navigation.test.js +5 -3
  64. package/dist/components/Input/popup-key-navigation.test.js.map +1 -1
  65. package/dist/components/Input/popup-props.d.ts.map +1 -1
  66. package/dist/components/Input/popup-props.js +4 -0
  67. package/dist/components/Input/popup-props.js.map +1 -1
  68. package/dist/components/Input/popup-props.test.d.ts +2 -0
  69. package/dist/components/Input/popup-props.test.d.ts.map +1 -0
  70. package/dist/components/Input/popup-props.test.js +70 -0
  71. package/dist/components/Input/popup-props.test.js.map +1 -0
  72. package/dist/components/InputBox.d.ts +0 -1
  73. package/dist/components/InputBox.d.ts.map +1 -1
  74. package/dist/components/InputBox.js +66 -14
  75. package/dist/components/InputBox.js.map +1 -1
  76. package/dist/components/InputBox.test.js +192 -12
  77. package/dist/components/InputBox.test.js.map +1 -1
  78. package/dist/components/MessageList.d.ts +1 -0
  79. package/dist/components/MessageList.d.ts.map +1 -1
  80. package/dist/components/MessageList.js.map +1 -1
  81. package/dist/components/ModelSelectionMenu.d.ts.map +1 -1
  82. package/dist/components/ModelSelectionMenu.debug.test.d.ts +2 -0
  83. package/dist/components/ModelSelectionMenu.debug.test.d.ts.map +1 -0
  84. package/dist/components/ModelSelectionMenu.debug.test.js.map +1 -0
  85. package/dist/components/ModelSelectionMenu.js +29 -125
  86. package/dist/components/ModelSelectionMenu.js.map +1 -1
  87. package/dist/components/ModelSelectionMenu.test.js +9 -5
  88. package/dist/components/ModelSelectionMenu.test.js.map +1 -1
  89. package/dist/components/PathSelectionMenu.d.ts.map +1 -1
  90. package/dist/components/PathSelectionMenu.js +15 -55
  91. package/dist/components/PathSelectionMenu.js.map +1 -1
  92. package/dist/components/SettingsSelectionMenu.d.ts +4 -1
  93. package/dist/components/SettingsSelectionMenu.d.ts.map +1 -1
  94. package/dist/components/SettingsSelectionMenu.js +48 -104
  95. package/dist/components/SettingsSelectionMenu.js.map +1 -1
  96. package/dist/components/SettingsSelectionMenu.scroll.test.js +140 -0
  97. package/dist/components/SettingsSelectionMenu.scroll.test.js.map +1 -1
  98. package/dist/components/SettingsSelectionMenu.test.js +38 -5
  99. package/dist/components/SettingsSelectionMenu.test.js.map +1 -1
  100. package/dist/components/SettingsValueSelectionMenu.d.ts.map +1 -1
  101. package/dist/components/SettingsValueSelectionMenu.js +24 -51
  102. package/dist/components/SettingsValueSelectionMenu.js.map +1 -1
  103. package/dist/components/SlashCommandMenu.d.ts +1 -0
  104. package/dist/components/SlashCommandMenu.d.ts.map +1 -1
  105. package/dist/components/SlashCommandMenu.js +11 -14
  106. package/dist/components/SlashCommandMenu.js.map +1 -1
  107. package/dist/components/StatusBar.d.ts +2 -0
  108. package/dist/components/StatusBar.d.ts.map +1 -1
  109. package/dist/components/StatusBar.js +35 -2
  110. package/dist/components/StatusBar.js.map +1 -1
  111. package/dist/components/StatusBar.test.js +32 -0
  112. package/dist/components/StatusBar.test.js.map +1 -1
  113. package/dist/components/UndoSelectionMenu.d.ts.map +1 -1
  114. package/dist/components/UndoSelectionMenu.js +6 -33
  115. package/dist/components/UndoSelectionMenu.js.map +1 -1
  116. package/dist/context/InputContext.stability.test.js +9 -7
  117. package/dist/context/InputContext.stability.test.js.map +1 -1
  118. package/dist/env-setup.js +1 -0
  119. package/dist/env-setup.js.map +1 -1
  120. package/dist/hooks/use-app-commands.d.ts +22 -2
  121. package/dist/hooks/use-app-commands.d.ts.map +1 -1
  122. package/dist/hooks/use-app-commands.js +43 -3
  123. package/dist/hooks/use-app-commands.js.map +1 -1
  124. package/dist/hooks/use-app-commands.test.js +135 -1
  125. package/dist/hooks/use-app-commands.test.js.map +1 -1
  126. package/dist/hooks/use-conversation.clear.test.d.ts +2 -0
  127. package/dist/hooks/use-conversation.clear.test.d.ts.map +1 -0
  128. package/dist/hooks/use-conversation.clear.test.js +54 -0
  129. package/dist/hooks/use-conversation.clear.test.js.map +1 -0
  130. package/dist/hooks/use-conversation.d.ts +25 -2
  131. package/dist/hooks/use-conversation.d.ts.map +1 -1
  132. package/dist/hooks/use-conversation.js +90 -12
  133. package/dist/hooks/use-conversation.js.map +1 -1
  134. package/dist/hooks/use-escape-key.d.ts +8 -1
  135. package/dist/hooks/use-escape-key.d.ts.map +1 -1
  136. package/dist/hooks/use-escape-key.js +15 -4
  137. package/dist/hooks/use-escape-key.js.map +1 -1
  138. package/dist/hooks/use-escape-key.test.js +135 -28
  139. package/dist/hooks/use-escape-key.test.js.map +1 -1
  140. package/dist/hooks/use-input-history.test.js +14 -8
  141. package/dist/hooks/use-input-history.test.js.map +1 -1
  142. package/dist/hooks/use-mode-handlers.d.ts +12 -1
  143. package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
  144. package/dist/hooks/use-mode-handlers.js +31 -1
  145. package/dist/hooks/use-mode-handlers.js.map +1 -1
  146. package/dist/hooks/use-model-selection.d.ts +6 -1
  147. package/dist/hooks/use-model-selection.d.ts.map +1 -1
  148. package/dist/hooks/use-model-selection.js +50 -8
  149. package/dist/hooks/use-model-selection.js.map +1 -1
  150. package/dist/hooks/use-model-selection.test.js +306 -50
  151. package/dist/hooks/use-model-selection.test.js.map +1 -1
  152. package/dist/hooks/use-path-completion.d.ts +4 -0
  153. package/dist/hooks/use-path-completion.d.ts.map +1 -1
  154. package/dist/hooks/use-path-completion.js +23 -17
  155. package/dist/hooks/use-path-completion.js.map +1 -1
  156. package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
  157. package/dist/hooks/use-runtime-settings.js +7 -2
  158. package/dist/hooks/use-runtime-settings.js.map +1 -1
  159. package/dist/hooks/use-runtime-settings.test.d.ts +2 -0
  160. package/dist/hooks/use-runtime-settings.test.d.ts.map +1 -0
  161. package/dist/hooks/use-runtime-settings.test.js +45 -0
  162. package/dist/hooks/use-runtime-settings.test.js.map +1 -0
  163. package/dist/hooks/use-selection.d.ts +6 -2
  164. package/dist/hooks/use-selection.d.ts.map +1 -1
  165. package/dist/hooks/use-selection.js +53 -16
  166. package/dist/hooks/use-selection.js.map +1 -1
  167. package/dist/hooks/use-selection.test.d.ts +2 -0
  168. package/dist/hooks/use-selection.test.d.ts.map +1 -0
  169. package/dist/hooks/use-selection.test.js +132 -0
  170. package/dist/hooks/use-selection.test.js.map +1 -0
  171. package/dist/hooks/use-settings-completion.d.ts +19 -0
  172. package/dist/hooks/use-settings-completion.d.ts.map +1 -1
  173. package/dist/hooks/use-settings-completion.js +106 -18
  174. package/dist/hooks/use-settings-completion.js.map +1 -1
  175. package/dist/hooks/use-settings-completion.scroll.test.js +68 -29
  176. package/dist/hooks/use-settings-completion.scroll.test.js.map +1 -1
  177. package/dist/hooks/use-settings-completion.test.js +36 -1
  178. package/dist/hooks/use-settings-completion.test.js.map +1 -1
  179. package/dist/hooks/use-settings-value-completion.d.ts +7 -1
  180. package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
  181. package/dist/hooks/use-settings-value-completion.js +18 -7
  182. package/dist/hooks/use-settings-value-completion.js.map +1 -1
  183. package/dist/hooks/use-slash-commands.d.ts +5 -0
  184. package/dist/hooks/use-slash-commands.d.ts.map +1 -1
  185. package/dist/hooks/use-slash-commands.js +22 -2
  186. package/dist/hooks/use-slash-commands.js.map +1 -1
  187. package/dist/hooks/use-trigger-detection.test.js +9 -6
  188. package/dist/hooks/use-trigger-detection.test.js.map +1 -1
  189. package/dist/hooks/use-undo-selection.d.ts +4 -0
  190. package/dist/hooks/use-undo-selection.d.ts.map +1 -1
  191. package/dist/hooks/use-undo-selection.js +18 -1
  192. package/dist/hooks/use-undo-selection.js.map +1 -1
  193. package/dist/inspect_zod.d.ts +2 -0
  194. package/dist/inspect_zod.d.ts.map +1 -0
  195. package/dist/inspect_zod.js +16 -0
  196. package/dist/inspect_zod.js.map +1 -0
  197. package/dist/lib/openai-agent-client.chat.test.js +40 -0
  198. package/dist/lib/openai-agent-client.chat.test.js.map +1 -1
  199. package/dist/lib/openai-agent-client.d.ts +18 -4
  200. package/dist/lib/openai-agent-client.d.ts.map +1 -1
  201. package/dist/lib/openai-agent-client.js +228 -32
  202. package/dist/lib/openai-agent-client.js.map +1 -1
  203. package/dist/lib/openai-agent-client.public-methods.test.js +193 -0
  204. package/dist/lib/openai-agent-client.public-methods.test.js.map +1 -1
  205. package/dist/lib/openai-agent-client.test.js +71 -1
  206. package/dist/lib/openai-agent-client.test.js.map +1 -1
  207. package/dist/lib/retry-executor.js +45 -7
  208. package/dist/lib/retry-executor.js.map +1 -1
  209. package/dist/lib/retry-executor.test.js +63 -0
  210. package/dist/lib/retry-executor.test.js.map +1 -1
  211. package/dist/lib/tool-invoke.d.ts +3 -2
  212. package/dist/lib/tool-invoke.d.ts.map +1 -1
  213. package/dist/lib/tool-invoke.js +213 -10
  214. package/dist/lib/tool-invoke.js.map +1 -1
  215. package/dist/lib/tool-invoke.test.js +103 -0
  216. package/dist/lib/tool-invoke.test.js.map +1 -1
  217. package/dist/non-interactive.d.ts +1 -0
  218. package/dist/non-interactive.d.ts.map +1 -1
  219. package/dist/non-interactive.js +3 -1
  220. package/dist/non-interactive.js.map +1 -1
  221. package/dist/non-interactive.test.js +8 -1
  222. package/dist/non-interactive.test.js.map +1 -1
  223. package/dist/prompts/codex.md +105 -97
  224. package/dist/prompts/fragments/gpt-5.3-codex.md +7 -0
  225. package/dist/prompts/fragments/gpt-5.4-small.md +6 -0
  226. package/dist/prompts/fragments/gpt-5.4.md +7 -0
  227. package/dist/prompts/fragments/gpt-5.5.md +7 -0
  228. package/dist/prompts/gpt-5-modern.md +96 -54
  229. package/dist/prompts/plan-mode-info.md +14 -3
  230. package/dist/prompts/prompt-constructor.d.ts +19 -0
  231. package/dist/prompts/prompt-constructor.d.ts.map +1 -0
  232. package/dist/prompts/prompt-constructor.js +53 -0
  233. package/dist/prompts/prompt-constructor.js.map +1 -0
  234. package/dist/prompts/prompt-constructor.test.d.ts +2 -0
  235. package/dist/prompts/prompt-constructor.test.d.ts.map +1 -0
  236. package/dist/prompts/prompt-constructor.test.js +99 -0
  237. package/dist/prompts/prompt-constructor.test.js.map +1 -0
  238. package/dist/prompts/prompt-constructor.test.ts +112 -0
  239. package/dist/prompts/prompt-constructor.ts +91 -0
  240. package/dist/prompts/prompt-profiles.d.ts +17 -0
  241. package/dist/prompts/prompt-profiles.d.ts.map +1 -0
  242. package/dist/prompts/prompt-profiles.js +61 -0
  243. package/dist/prompts/prompt-profiles.js.map +1 -0
  244. package/dist/prompts/prompt-profiles.ts +77 -0
  245. package/dist/prompts/prompt-selector.d.ts.map +1 -1
  246. package/dist/prompts/prompt-selector.js +3 -21
  247. package/dist/prompts/prompt-selector.js.map +1 -1
  248. package/dist/prompts/prompt-selector.ts +3 -25
  249. package/dist/prompts/reasoning-efficiency.d.ts.map +1 -1
  250. package/dist/prompts/reasoning-efficiency.js +2 -0
  251. package/dist/prompts/reasoning-efficiency.js.map +1 -1
  252. package/dist/prompts/reasoning-efficiency.test.d.ts +2 -0
  253. package/dist/prompts/reasoning-efficiency.test.d.ts.map +1 -0
  254. package/dist/prompts/reasoning-efficiency.test.js +11 -0
  255. package/dist/prompts/reasoning-efficiency.test.js.map +1 -0
  256. package/dist/prompts/reasoning-efficiency.test.ts +12 -0
  257. package/dist/prompts/reasoning-efficiency.ts +2 -0
  258. package/dist/prompts/search-via-shell.test.js +19 -16
  259. package/dist/prompts/search-via-shell.test.js.map +1 -1
  260. package/dist/prompts/search-via-shell.test.ts +20 -16
  261. package/dist/prompts/subagent-delegation.test.js +12 -10
  262. package/dist/prompts/subagent-delegation.test.js.map +1 -1
  263. package/dist/prompts/subagent-delegation.test.ts +13 -10
  264. package/dist/prompts/worktree-hygiene.md +9 -0
  265. package/dist/providers/ai-sdk-logging-fetch.d.ts +1 -1
  266. package/dist/providers/ai-sdk-logging-fetch.d.ts.map +1 -1
  267. package/dist/providers/ai-sdk-logging-fetch.js +145 -47
  268. package/dist/providers/ai-sdk-logging-fetch.js.map +1 -1
  269. package/dist/providers/ai-sdk-logging-fetch.test.js +202 -5
  270. package/dist/providers/ai-sdk-logging-fetch.test.js.map +1 -1
  271. package/dist/providers/codex-responses-model.d.ts +20 -0
  272. package/dist/providers/codex-responses-model.d.ts.map +1 -0
  273. package/dist/providers/codex-responses-model.js +170 -0
  274. package/dist/providers/codex-responses-model.js.map +1 -0
  275. package/dist/providers/codex-responses-model.test.d.ts +2 -0
  276. package/dist/providers/codex-responses-model.test.d.ts.map +1 -0
  277. package/dist/providers/codex-responses-model.test.js +240 -0
  278. package/dist/providers/codex-responses-model.test.js.map +1 -0
  279. package/dist/providers/codex.provider.d.ts +29 -0
  280. package/dist/providers/codex.provider.d.ts.map +1 -0
  281. package/dist/providers/codex.provider.js +486 -0
  282. package/dist/providers/codex.provider.js.map +1 -0
  283. package/dist/providers/codex.provider.test.d.ts +2 -0
  284. package/dist/providers/codex.provider.test.d.ts.map +1 -0
  285. package/dist/providers/codex.provider.test.js +634 -0
  286. package/dist/providers/codex.provider.test.js.map +1 -0
  287. package/dist/providers/custom-provider-adapter.test.js +56 -0
  288. package/dist/providers/custom-provider-adapter.test.js.map +1 -1
  289. package/dist/providers/fallback-responses-model.d.ts +20 -0
  290. package/dist/providers/fallback-responses-model.d.ts.map +1 -0
  291. package/dist/providers/fallback-responses-model.js +277 -0
  292. package/dist/providers/fallback-responses-model.js.map +1 -0
  293. package/dist/providers/fallback-responses-model.test.d.ts +2 -0
  294. package/dist/providers/fallback-responses-model.test.d.ts.map +1 -0
  295. package/dist/providers/fallback-responses-model.test.js +279 -0
  296. package/dist/providers/fallback-responses-model.test.js.map +1 -0
  297. package/dist/providers/fetch/compose.d.ts +7 -0
  298. package/dist/providers/fetch/compose.d.ts.map +1 -0
  299. package/dist/providers/fetch/compose.js +20 -0
  300. package/dist/providers/fetch/compose.js.map +1 -0
  301. package/dist/providers/fetch/composer.d.ts +13 -0
  302. package/dist/providers/fetch/composer.d.ts.map +1 -0
  303. package/dist/providers/fetch/composer.js +15 -0
  304. package/dist/providers/fetch/composer.js.map +1 -0
  305. package/dist/providers/fetch/composer.test.d.ts +2 -0
  306. package/dist/providers/fetch/composer.test.d.ts.map +1 -0
  307. package/dist/providers/fetch/composer.test.js +448 -0
  308. package/dist/providers/fetch/composer.test.js.map +1 -0
  309. package/dist/providers/fetch/logging-middleware.d.ts +16 -0
  310. package/dist/providers/fetch/logging-middleware.d.ts.map +1 -0
  311. package/dist/providers/fetch/logging-middleware.js +214 -0
  312. package/dist/providers/fetch/logging-middleware.js.map +1 -0
  313. package/dist/providers/index.d.ts +1 -0
  314. package/dist/providers/index.d.ts.map +1 -1
  315. package/dist/providers/index.js +1 -0
  316. package/dist/providers/index.js.map +1 -1
  317. package/dist/providers/llama-cpp.provider.d.ts +3 -0
  318. package/dist/providers/llama-cpp.provider.d.ts.map +1 -0
  319. package/dist/providers/llama-cpp.provider.js +34 -0
  320. package/dist/providers/llama-cpp.provider.js.map +1 -0
  321. package/dist/providers/openai-compatible-middleware.d.ts +7 -0
  322. package/dist/providers/openai-compatible-middleware.d.ts.map +1 -0
  323. package/dist/providers/openai-compatible-middleware.js +133 -0
  324. package/dist/providers/openai-compatible-middleware.js.map +1 -0
  325. package/dist/providers/openai-compatible-models.d.ts +6 -0
  326. package/dist/providers/openai-compatible-models.d.ts.map +1 -0
  327. package/dist/providers/openai-compatible-models.js +17 -0
  328. package/dist/providers/openai-compatible-models.js.map +1 -0
  329. package/dist/providers/openai-compatible-response-normalizer.d.ts +7 -0
  330. package/dist/providers/openai-compatible-response-normalizer.d.ts.map +1 -0
  331. package/dist/providers/openai-compatible-response-normalizer.js +58 -0
  332. package/dist/providers/openai-compatible-response-normalizer.js.map +1 -0
  333. package/dist/providers/openai-compatible.provider.d.ts +4 -1
  334. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  335. package/dist/providers/openai-compatible.provider.js +68 -324
  336. package/dist/providers/openai-compatible.provider.js.map +1 -1
  337. package/dist/providers/openai-compatible.provider.test.js +106 -1
  338. package/dist/providers/openai-compatible.provider.test.js.map +1 -1
  339. package/dist/providers/openai.provider.js +53 -1
  340. package/dist/providers/openai.provider.js.map +1 -1
  341. package/dist/providers/opencode.provider.d.ts +19 -0
  342. package/dist/providers/opencode.provider.d.ts.map +1 -0
  343. package/dist/providers/opencode.provider.js +45 -0
  344. package/dist/providers/opencode.provider.js.map +1 -0
  345. package/dist/providers/openrouter.provider.d.ts +2 -1
  346. package/dist/providers/openrouter.provider.d.ts.map +1 -1
  347. package/dist/providers/openrouter.provider.js +39 -22
  348. package/dist/providers/openrouter.provider.js.map +1 -1
  349. package/dist/providers/openrouter.provider.request-preprocessing.test.d.ts +2 -0
  350. package/dist/providers/openrouter.provider.request-preprocessing.test.d.ts.map +1 -0
  351. package/dist/providers/openrouter.provider.request-preprocessing.test.js +51 -0
  352. package/dist/providers/openrouter.provider.request-preprocessing.test.js.map +1 -0
  353. package/dist/providers/registry.d.ts +1 -0
  354. package/dist/providers/registry.d.ts.map +1 -1
  355. package/dist/providers/registry.js.map +1 -1
  356. package/dist/providers/registry.test.js +11 -2
  357. package/dist/providers/registry.test.js.map +1 -1
  358. package/dist/scripts/eval-auto-approval/dataset.d.ts +3 -3
  359. package/dist/services/approval-flow-coordinator.d.ts.map +1 -1
  360. package/dist/services/approval-flow-coordinator.js +17 -1
  361. package/dist/services/approval-flow-coordinator.js.map +1 -1
  362. package/dist/services/approval-flow-coordinator.test.js +22 -0
  363. package/dist/services/approval-flow-coordinator.test.js.map +1 -1
  364. package/dist/services/command-message-streaming.d.ts.map +1 -1
  365. package/dist/services/command-message-streaming.js +0 -3
  366. package/dist/services/command-message-streaming.js.map +1 -1
  367. package/dist/services/command-message-streaming.test.js +4 -2
  368. package/dist/services/command-message-streaming.test.js.map +1 -1
  369. package/dist/services/conversation-clear-save.test.d.ts +2 -0
  370. package/dist/services/conversation-clear-save.test.d.ts.map +1 -0
  371. package/dist/services/conversation-clear-save.test.js +57 -0
  372. package/dist/services/conversation-clear-save.test.js.map +1 -0
  373. package/dist/services/conversation-events.d.ts +44 -1
  374. package/dist/services/conversation-events.d.ts.map +1 -1
  375. package/dist/services/conversation-history-repair.d.ts +25 -0
  376. package/dist/services/conversation-history-repair.d.ts.map +1 -0
  377. package/dist/services/conversation-history-repair.js +227 -0
  378. package/dist/services/conversation-history-repair.js.map +1 -0
  379. package/dist/services/conversation-history-repair.test.d.ts +2 -0
  380. package/dist/services/conversation-history-repair.test.d.ts.map +1 -0
  381. package/dist/services/conversation-history-repair.test.js +76 -0
  382. package/dist/services/conversation-history-repair.test.js.map +1 -0
  383. package/dist/services/conversation-integration.test.js +3 -1
  384. package/dist/services/conversation-integration.test.js.map +1 -1
  385. package/dist/services/conversation-log-events.d.ts +107 -0
  386. package/dist/services/conversation-log-events.d.ts.map +1 -0
  387. package/dist/services/conversation-log-events.js +12 -0
  388. package/dist/services/conversation-log-events.js.map +1 -0
  389. package/dist/services/conversation-log-writer.d.ts +40 -0
  390. package/dist/services/conversation-log-writer.d.ts.map +1 -0
  391. package/dist/services/conversation-log-writer.js +264 -0
  392. package/dist/services/conversation-log-writer.js.map +1 -0
  393. package/dist/services/conversation-persistence-types.d.ts +14 -0
  394. package/dist/services/conversation-persistence-types.d.ts.map +1 -0
  395. package/dist/services/conversation-persistence-types.js +2 -0
  396. package/dist/services/conversation-persistence-types.js.map +1 -0
  397. package/dist/services/conversation-persistence.d.ts +40 -38
  398. package/dist/services/conversation-persistence.d.ts.map +1 -1
  399. package/dist/services/conversation-persistence.js +312 -71
  400. package/dist/services/conversation-persistence.js.map +1 -1
  401. package/dist/services/conversation-persistence.test.js +485 -355
  402. package/dist/services/conversation-persistence.test.js.map +1 -1
  403. package/dist/services/conversation-replay.d.ts +26 -0
  404. package/dist/services/conversation-replay.d.ts.map +1 -0
  405. package/dist/services/conversation-replay.js +252 -0
  406. package/dist/services/conversation-replay.js.map +1 -0
  407. package/dist/services/conversation-replay.test.d.ts +2 -0
  408. package/dist/services/conversation-replay.test.d.ts.map +1 -0
  409. package/dist/services/conversation-replay.test.js +153 -0
  410. package/dist/services/conversation-replay.test.js.map +1 -0
  411. package/dist/services/conversation-result-builder.d.ts.map +1 -1
  412. package/dist/services/conversation-result-builder.js +8 -2
  413. package/dist/services/conversation-result-builder.js.map +1 -1
  414. package/dist/services/conversation-result-builder.test.js +53 -0
  415. package/dist/services/conversation-result-builder.test.js.map +1 -1
  416. package/dist/services/conversation-retry-policy.d.ts +24 -0
  417. package/dist/services/conversation-retry-policy.d.ts.map +1 -1
  418. package/dist/services/conversation-retry-policy.js +68 -4
  419. package/dist/services/conversation-retry-policy.js.map +1 -1
  420. package/dist/services/conversation-retry-policy.test.js +99 -2
  421. package/dist/services/conversation-retry-policy.test.js.map +1 -1
  422. package/dist/services/conversation-service.d.ts +12 -2
  423. package/dist/services/conversation-service.d.ts.map +1 -1
  424. package/dist/services/conversation-service.js +29 -3
  425. package/dist/services/conversation-service.js.map +1 -1
  426. package/dist/services/conversation-session.auto-approval.test.js +2 -1
  427. package/dist/services/conversation-session.auto-approval.test.js.map +1 -1
  428. package/dist/services/conversation-session.d.ts +17 -1
  429. package/dist/services/conversation-session.d.ts.map +1 -1
  430. package/dist/services/conversation-session.js +580 -134
  431. package/dist/services/conversation-session.js.map +1 -1
  432. package/dist/services/conversation-store.d.ts.map +1 -1
  433. package/dist/services/conversation-store.js +6 -5
  434. package/dist/services/conversation-store.js.map +1 -1
  435. package/dist/services/conversation-store.test.js +34 -0
  436. package/dist/services/conversation-store.test.js.map +1 -1
  437. package/dist/services/input-surge-guard.d.ts +4 -0
  438. package/dist/services/input-surge-guard.d.ts.map +1 -1
  439. package/dist/services/input-surge-guard.js +2 -2
  440. package/dist/services/input-surge-guard.js.map +1 -1
  441. package/dist/services/input-surge-guard.test.js +15 -1
  442. package/dist/services/input-surge-guard.test.js.map +1 -1
  443. package/dist/services/large-uncached-input-guard.d.ts +35 -0
  444. package/dist/services/large-uncached-input-guard.d.ts.map +1 -0
  445. package/dist/services/large-uncached-input-guard.js +127 -0
  446. package/dist/services/large-uncached-input-guard.js.map +1 -0
  447. package/dist/services/large-uncached-input-guard.test.d.ts +2 -0
  448. package/dist/services/large-uncached-input-guard.test.d.ts.map +1 -0
  449. package/dist/services/large-uncached-input-guard.test.js +160 -0
  450. package/dist/services/large-uncached-input-guard.test.js.map +1 -0
  451. package/dist/services/logging-contract.d.ts +1 -1
  452. package/dist/services/logging-service.d.ts +4 -0
  453. package/dist/services/logging-service.d.ts.map +1 -1
  454. package/dist/services/logging-service.js +91 -33
  455. package/dist/services/logging-service.js.map +1 -1
  456. package/dist/services/model-service.d.ts +2 -0
  457. package/dist/services/model-service.d.ts.map +1 -1
  458. package/dist/services/model-service.js +7 -1
  459. package/dist/services/model-service.js.map +1 -1
  460. package/dist/services/provider-traffic.d.ts +87 -0
  461. package/dist/services/provider-traffic.d.ts.map +1 -0
  462. package/dist/services/provider-traffic.js +603 -0
  463. package/dist/services/provider-traffic.js.map +1 -0
  464. package/dist/services/provider-traffic.test.d.ts +2 -0
  465. package/dist/services/provider-traffic.test.d.ts.map +1 -0
  466. package/dist/services/provider-traffic.test.js +475 -0
  467. package/dist/services/provider-traffic.test.js.map +1 -0
  468. package/dist/services/rtk-service.d.ts.map +1 -1
  469. package/dist/services/rtk-service.js +1 -2
  470. package/dist/services/rtk-service.js.map +1 -1
  471. package/dist/services/service-interfaces.d.ts +16 -0
  472. package/dist/services/service-interfaces.d.ts.map +1 -1
  473. package/dist/services/settings-schema.d.ts +36 -28
  474. package/dist/services/settings-schema.d.ts.map +1 -1
  475. package/dist/services/settings-schema.js +7 -2
  476. package/dist/services/settings-schema.js.map +1 -1
  477. package/dist/services/settings-service.js +6 -6
  478. package/dist/services/settings-service.js.map +1 -1
  479. package/dist/services/settings-sources.d.ts.map +1 -1
  480. package/dist/services/settings-sources.js +1 -0
  481. package/dist/services/settings-sources.js.map +1 -1
  482. package/dist/services/shell-auto-approval-evaluator.d.ts +1 -1
  483. package/dist/services/shell-auto-approval-evaluator.d.ts.map +1 -1
  484. package/dist/services/shell-auto-approval-evaluator.js +284 -81
  485. package/dist/services/shell-auto-approval-evaluator.js.map +1 -1
  486. package/dist/services/shell-auto-approval-evaluator.test.js +215 -15
  487. package/dist/services/shell-auto-approval-evaluator.test.js.map +1 -1
  488. package/dist/services/stream-event-processor.d.ts +4 -0
  489. package/dist/services/stream-event-processor.d.ts.map +1 -1
  490. package/dist/services/stream-event-processor.js +78 -39
  491. package/dist/services/stream-event-processor.js.map +1 -1
  492. package/dist/services/stream-event-processor.test.js +71 -0
  493. package/dist/services/stream-event-processor.test.js.map +1 -1
  494. package/dist/services/subagents/subagent-manager.d.ts +2 -0
  495. package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
  496. package/dist/services/subagents/subagent-manager.js +124 -21
  497. package/dist/services/subagents/subagent-manager.js.map +1 -1
  498. package/dist/services/subagents/subagent-manager.test.js +453 -0
  499. package/dist/services/subagents/subagent-manager.test.js.map +1 -1
  500. package/dist/services/terminal-result-collector.d.ts.map +1 -1
  501. package/dist/services/terminal-result-collector.js +6 -1
  502. package/dist/services/terminal-result-collector.js.map +1 -1
  503. package/dist/services/tool-call-arguments.d.ts +20 -0
  504. package/dist/services/tool-call-arguments.d.ts.map +1 -0
  505. package/dist/services/tool-call-arguments.js +49 -0
  506. package/dist/services/tool-call-arguments.js.map +1 -0
  507. package/dist/services/tool-execution-ledger.d.ts +35 -0
  508. package/dist/services/tool-execution-ledger.d.ts.map +1 -0
  509. package/dist/services/tool-execution-ledger.js +204 -0
  510. package/dist/services/tool-execution-ledger.js.map +1 -0
  511. package/dist/services/tool-execution-ledger.test.d.ts +2 -0
  512. package/dist/services/tool-execution-ledger.test.d.ts.map +1 -0
  513. package/dist/services/tool-execution-ledger.test.js +96 -0
  514. package/dist/services/tool-execution-ledger.test.js.map +1 -0
  515. package/dist/tools/apply-patch.d.ts +8 -0
  516. package/dist/tools/apply-patch.d.ts.map +1 -1
  517. package/dist/tools/apply-patch.js +157 -2
  518. package/dist/tools/apply-patch.js.map +1 -1
  519. package/dist/tools/apply-patch.test.js +81 -0
  520. package/dist/tools/apply-patch.test.js.map +1 -1
  521. package/dist/tools/search-replace.d.ts.map +1 -1
  522. package/dist/tools/search-replace.js +92 -140
  523. package/dist/tools/search-replace.js.map +1 -1
  524. package/dist/utils/command-safety/path-analysis.d.ts.map +1 -1
  525. package/dist/utils/command-safety/path-analysis.js +143 -35
  526. package/dist/utils/command-safety/path-analysis.js.map +1 -1
  527. package/dist/utils/conversation-event-handler.d.ts.map +1 -1
  528. package/dist/utils/conversation-event-handler.js +52 -4
  529. package/dist/utils/conversation-event-handler.js.map +1 -1
  530. package/dist/utils/conversation-event-handler.test.js +175 -0
  531. package/dist/utils/conversation-event-handler.test.js.map +1 -1
  532. package/dist/utils/error-helpers.d.ts +1 -0
  533. package/dist/utils/error-helpers.d.ts.map +1 -1
  534. package/dist/utils/error-helpers.js +61 -0
  535. package/dist/utils/error-helpers.js.map +1 -1
  536. package/dist/utils/error-helpers.test.js +13 -1
  537. package/dist/utils/error-helpers.test.js.map +1 -1
  538. package/dist/utils/ink-render-options.d.ts +1 -1
  539. package/dist/utils/ink-render-options.d.ts.map +1 -1
  540. package/dist/utils/ink-render-options.js +1 -1
  541. package/dist/utils/ink-render-options.js.map +1 -1
  542. package/dist/utils/streaming-session-factory.d.ts +2 -0
  543. package/dist/utils/streaming-session-factory.d.ts.map +1 -1
  544. package/dist/utils/streaming-session-factory.js +4 -0
  545. package/dist/utils/streaming-session-factory.js.map +1 -1
  546. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { ConversationStore } from './conversation-store.js';
2
- import { decideRetry, MAX_HALLUCINATION_RETRIES } from './conversation-retry-policy.js';
2
+ import { decideRetry, isTransientRetryableError, MAX_HALLUCINATION_RETRIES, MAX_TRANSIENT_RETRIES, } from './conversation-retry-policy.js';
3
3
  import { getProvider } from '../providers/index.js';
4
4
  import { ApprovalState } from './approval-state.js';
5
5
  import { collectTerminalResult } from './terminal-result-collector.js';
@@ -9,13 +9,68 @@ import { ShellAutoApprovalResolver } from './shell-auto-approval-resolver.js';
9
9
  import { ApprovalFlowCoordinator } from './approval-flow-coordinator.js';
10
10
  import { buildConversationResult, toTerminalEvent } from './conversation-result-builder.js';
11
11
  import { normalizeUserTurn } from '../types/user-turn.js';
12
- import { InputSurgeGuard } from './input-surge-guard.js';
12
+ import { collectDuplicateToolCallResultPairs, InputSurgeGuard } from './input-surge-guard.js';
13
+ import { LargeUncachedInputGuard } from './large-uncached-input-guard.js';
14
+ import { reconcileHistoryWithToolLedger, ToolExecutionLedger, } from './tool-execution-ledger.js';
15
+ import { describeError } from '../utils/error-helpers.js';
16
+ const asRecordLocal = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;
17
+ const rawItemLocal = (item) => {
18
+ const rec = asRecordLocal(item);
19
+ if (!rec)
20
+ return null;
21
+ return asRecordLocal(rec.rawItem) ?? rec;
22
+ };
23
+ const callIdOfLocal = (item) => {
24
+ const raw = rawItemLocal(item);
25
+ const cid = raw?.callId ?? raw?.call_id ?? raw?.tool_call_id ?? raw?.toolCallId ?? raw?.id;
26
+ return typeof cid === 'string' && cid ? cid : null;
27
+ };
28
+ const toolNameOfLocal = (item) => {
29
+ const raw = rawItemLocal(item);
30
+ const name = raw?.name ?? asRecordLocal(item)?.name;
31
+ return typeof name === 'string' && name ? name : 'unknown';
32
+ };
33
+ const outputOfLocal = (item) => {
34
+ const raw = rawItemLocal(item);
35
+ return raw?.output ?? asRecordLocal(item)?.output;
36
+ };
13
37
  const supportsConversationChaining = (providerId) => {
14
38
  const providerDef = getProvider(providerId);
15
39
  return providerDef?.capabilities?.supportsConversationChaining ?? false;
16
40
  };
41
+ const asArray = (value) => (Array.isArray(value) ? value : []);
42
+ const warnIfStreamHistoryReplayedTools = ({ logger, sessionId, source, stream, }) => {
43
+ const streamRecord = stream;
44
+ const history = asArray(streamRecord.history);
45
+ const newItems = asArray(streamRecord.newItems);
46
+ const stateGeneratedItems = asArray(streamRecord.state?._generatedItems);
47
+ const historyDuplicates = collectDuplicateToolCallResultPairs(history);
48
+ const newItemsDuplicates = collectDuplicateToolCallResultPairs(newItems);
49
+ const stateGeneratedItemsDuplicates = collectDuplicateToolCallResultPairs(stateGeneratedItems);
50
+ if (historyDuplicates.pairs === 0 && newItemsDuplicates.pairs === 0 && stateGeneratedItemsDuplicates.pairs === 0) {
51
+ return;
52
+ }
53
+ logger.warn('Completed stream history contains replayed tool call/result pairs', {
54
+ eventType: 'conversation.stream_history.replayed_tools',
55
+ category: 'provider',
56
+ phase: 'post_stream',
57
+ sessionId,
58
+ traceId: logger.getCorrelationId(),
59
+ source,
60
+ historyLength: history.length,
61
+ newItemsLength: newItems.length,
62
+ stateGeneratedItemsLength: stateGeneratedItems.length,
63
+ historyDuplicatePairs: historyDuplicates.pairs,
64
+ historyMaxCopies: historyDuplicates.maxCopies,
65
+ newItemsDuplicatePairs: newItemsDuplicates.pairs,
66
+ newItemsMaxCopies: newItemsDuplicates.maxCopies,
67
+ stateGeneratedItemsDuplicatePairs: stateGeneratedItemsDuplicates.pairs,
68
+ stateGeneratedItemsMaxCopies: stateGeneratedItemsDuplicates.maxCopies,
69
+ });
70
+ };
17
71
  export class ConversationSession {
18
72
  id;
73
+ startedAt;
19
74
  agentClient;
20
75
  logger;
21
76
  conversationStore;
@@ -23,14 +78,19 @@ export class ConversationSession {
23
78
  approvalState = new ApprovalState();
24
79
  toolCallArgumentsById = new Map();
25
80
  emittedInvalidToolCallPackets = new Set();
81
+ emittedToolStartedCallIds = new Set();
26
82
  shellAutoApproval;
27
83
  approvalFlow;
28
84
  inputSurgeGuard = new InputSurgeGuard();
85
+ largeUncachedInputGuard = new LargeUncachedInputGuard();
86
+ toolLedger = new ToolExecutionLedger();
29
87
  generation = 0;
30
88
  settingsService;
31
89
  pendingModeNotice = null;
32
- constructor(id, { agentClient, deps, }) {
90
+ logSink = null;
91
+ constructor(id, { agentClient, deps, sessionStartedAt, }) {
33
92
  this.id = id;
93
+ this.startedAt = sessionStartedAt ?? new Date().toISOString();
34
94
  this.agentClient = agentClient;
35
95
  this.logger = deps.logger;
36
96
  this.settingsService = deps.settingsService;
@@ -48,6 +108,107 @@ export class ConversationSession {
48
108
  sessionId: this.id,
49
109
  });
50
110
  }
111
+ #getTrafficMode() {
112
+ if (!this.settingsService)
113
+ return 'standard';
114
+ if (this.settingsService.get('app.orchestratorMode'))
115
+ return 'orchestrator';
116
+ if (this.settingsService.get('app.liteMode'))
117
+ return 'lite';
118
+ if (this.settingsService.get('app.planMode'))
119
+ return 'plan';
120
+ if (this.settingsService.get('app.mentorMode'))
121
+ return 'mentor';
122
+ return 'standard';
123
+ }
124
+ #getModelForGuard() {
125
+ return this.settingsService?.get('agent.model') ?? null;
126
+ }
127
+ #getReasoningEffortForGuard() {
128
+ return this.settingsService?.get('agent.reasoningEffort') ?? null;
129
+ }
130
+ #getProviderForGuard() {
131
+ const getProvider = getMethod(this.agentClient, 'getProvider');
132
+ return getProvider
133
+ ? getProvider.call(this.agentClient)
134
+ : this.settingsService?.get('agent.provider') ?? null;
135
+ }
136
+ #makeUserInputItem(turn) {
137
+ const images = turn.images ?? [];
138
+ if (images.length === 0) {
139
+ return { role: 'user', type: 'message', content: turn.text ?? '' };
140
+ }
141
+ const content = [];
142
+ if (turn.text) {
143
+ content.push({ type: 'input_text', text: turn.text });
144
+ }
145
+ for (const image of images) {
146
+ content.push({
147
+ type: 'input_image',
148
+ image: `data:${image.mimeType};base64,${image.data}`,
149
+ detail: 'auto',
150
+ });
151
+ }
152
+ return { role: 'user', type: 'message', content };
153
+ }
154
+ #buildOutgoingInput(turn, { includeTurn }) {
155
+ const provider = this.#getProviderForGuard() ?? 'openai';
156
+ const supportsChaining = supportsConversationChaining(provider);
157
+ const history = this.conversationStore.getHistory();
158
+ const outgoingHistory = includeTurn ? [...history, this.#makeUserInputItem(turn)] : history;
159
+ const useChaining = supportsChaining && (!!this.previousResponseId || outgoingHistory.length <= 1);
160
+ const notice = this.pendingModeNotice;
161
+ const latestInput = outgoingHistory[outgoingHistory.length - 1] ?? turn.text;
162
+ const chainedInput = turn.images?.length ? latestInput : turn.text;
163
+ if (notice) {
164
+ if (useChaining) {
165
+ const userItem = typeof chainedInput === 'string' ? { role: 'user', type: 'message', content: chainedInput } : chainedInput;
166
+ const noticeItem = { role: 'system', type: 'message', content: notice };
167
+ return {
168
+ streamInput: [noticeItem, userItem],
169
+ inputSurgeKind: 'delta',
170
+ };
171
+ }
172
+ const noticeItem = { role: 'user', type: 'message', content: notice };
173
+ return {
174
+ streamInput: [...outgoingHistory, noticeItem],
175
+ inputSurgeKind: 'full_history',
176
+ modeNoticeToPersist: notice,
177
+ };
178
+ }
179
+ return {
180
+ streamInput: useChaining ? (typeof chainedInput === 'string' ? chainedInput : [chainedInput]) : outgoingHistory,
181
+ inputSurgeKind: useChaining ? 'delta' : 'full_history',
182
+ };
183
+ }
184
+ previewLargeUncachedInput(input, now = Date.now()) {
185
+ const turn = normalizeUserTurn(input);
186
+ const { streamInput } = this.#buildOutgoingInput(turn, { includeTurn: true });
187
+ return this.largeUncachedInputGuard.inspect({
188
+ input: streamInput,
189
+ now,
190
+ provider: this.#getProviderForGuard(),
191
+ model: this.#getModelForGuard(),
192
+ reasoningEffort: this.#getReasoningEffortForGuard(),
193
+ mode: this.#getTrafficMode(),
194
+ });
195
+ }
196
+ #getFirstUserMessagePreview(currentTurn) {
197
+ const [firstTurn] = this.conversationStore.listUserTurns();
198
+ return firstTurn?.text ?? currentTurn ?? '';
199
+ }
200
+ #withTrafficContext(currentTurn, fn) {
201
+ const runner = this.logger.runWithTrafficContext;
202
+ if (!runner)
203
+ return fn();
204
+ return runner.call(this.logger, {
205
+ sessionId: this.id,
206
+ sessionStartedAt: this.startedAt,
207
+ firstUserMessagePreview: this.#getFirstUserMessagePreview(currentTurn),
208
+ mode: this.#getTrafficMode(),
209
+ traceId: this.logger.getCorrelationId(),
210
+ }, fn);
211
+ }
51
212
  #isCurrentGeneration(gen) {
52
213
  return gen === this.generation;
53
214
  }
@@ -58,8 +219,11 @@ export class ConversationSession {
58
219
  this.approvalState.clearPending();
59
220
  this.approvalState.consumeAborted();
60
221
  this.toolCallArgumentsById.clear();
222
+ this.emittedToolStartedCallIds.clear();
223
+ this.toolLedger = new ToolExecutionLedger();
61
224
  this.shellAutoApproval.clearCache();
62
225
  this.inputSurgeGuard.reset();
226
+ this.largeUncachedInputGuard.reset();
63
227
  const clearConversations = getMethod(this.agentClient, 'clearConversations');
64
228
  clearConversations?.call(this.agentClient);
65
229
  }
@@ -72,8 +236,11 @@ export class ConversationSession {
72
236
  this.approvalState.clearPending();
73
237
  this.approvalState.consumeAborted();
74
238
  this.toolCallArgumentsById.clear();
239
+ this.emittedToolStartedCallIds.clear();
75
240
  this.shellAutoApproval.clearCache();
76
241
  this.inputSurgeGuard.reset();
242
+ this.largeUncachedInputGuard.markUndoOrRewind();
243
+ this.#log({ type: 'undo', removedUserTurns: 1, snapshot: this.getCurrentSnapshot() });
77
244
  return removed;
78
245
  }
79
246
  listUserTurns() {
@@ -88,8 +255,11 @@ export class ConversationSession {
88
255
  this.approvalState.clearPending();
89
256
  this.approvalState.consumeAborted();
90
257
  this.toolCallArgumentsById.clear();
258
+ this.emittedToolStartedCallIds.clear();
91
259
  this.shellAutoApproval.clearCache();
92
260
  this.inputSurgeGuard.reset();
261
+ this.largeUncachedInputGuard.markUndoOrRewind();
262
+ this.#log({ type: 'undo', removedUserTurns: n, snapshot: this.getCurrentSnapshot() });
93
263
  return removed;
94
264
  }
95
265
  setModel(model) {
@@ -104,27 +274,160 @@ export class ConversationSession {
104
274
  setTemperature?.call(this.agentClient, temperature);
105
275
  }
106
276
  setProvider(provider) {
277
+ this.generation++;
278
+ this.previousResponseId = null;
279
+ this.approvalState.clearPending();
280
+ this.approvalState.consumeAborted();
281
+ this.toolCallArgumentsById.clear();
282
+ this.emittedToolStartedCallIds.clear();
283
+ this.shellAutoApproval.clearCache();
284
+ this.inputSurgeGuard.reset();
285
+ const clearConversations = getMethod(this.agentClient, 'clearConversations');
286
+ clearConversations?.call(this.agentClient);
107
287
  const setProvider = getMethod(this.agentClient, 'setProvider');
108
288
  setProvider?.call(this.agentClient, provider);
109
289
  }
290
+ switchProvider(provider) {
291
+ this.setProvider(provider);
292
+ }
110
293
  setRetryCallback(callback) {
111
294
  if (typeof this.agentClient.setRetryCallback === 'function') {
112
295
  this.agentClient.setRetryCallback(callback);
113
296
  }
114
297
  }
298
+ setLogSink(sink) {
299
+ this.logSink = sink;
300
+ }
301
+ getCurrentSnapshot() {
302
+ const getProvider = getMethod(this.agentClient, 'getProvider');
303
+ const provider = getProvider
304
+ ? getProvider.call(this.agentClient)
305
+ : this.settingsService?.get('agent.provider');
306
+ const model = this.settingsService?.get('agent.model');
307
+ return {
308
+ history: this.conversationStore.getHistory(),
309
+ previousResponseId: this.previousResponseId,
310
+ toolLedger: this.toolLedger.export(),
311
+ ...(model ? { model } : {}),
312
+ ...(provider ? { provider } : {}),
313
+ };
314
+ }
315
+ #log(event) {
316
+ if (!this.logSink)
317
+ return;
318
+ try {
319
+ this.logSink(event);
320
+ }
321
+ catch (err) {
322
+ this.logger.warn('Conversation log sink threw', {
323
+ eventType: 'conversation_log.sink_failed',
324
+ category: 'persistence',
325
+ errorMessage: err instanceof Error ? err.message : String(err),
326
+ });
327
+ }
328
+ }
329
+ #dispatchEventToLog(event) {
330
+ if (!this.logSink)
331
+ return;
332
+ switch (event.type) {
333
+ case 'tool_started':
334
+ this.#log({
335
+ type: 'tool_started',
336
+ toolCallId: event.toolCallId,
337
+ toolName: event.toolName,
338
+ arguments: event.arguments,
339
+ });
340
+ return;
341
+ case 'command_message':
342
+ this.#log({ type: 'command_message', message: event.message });
343
+ return;
344
+ case 'approval_required':
345
+ this.#log({
346
+ type: 'approval_required',
347
+ approval: {
348
+ toolName: event.approval.toolName,
349
+ argumentsText: event.approval.argumentsText,
350
+ agentName: event.approval.agentName,
351
+ ...('callId' in event.approval && event.approval.callId ? { callId: event.approval.callId } : {}),
352
+ },
353
+ });
354
+ return;
355
+ case 'subagent_started':
356
+ this.#log({
357
+ type: 'subagent_started',
358
+ agentId: event.agentId,
359
+ role: event.role,
360
+ task: event.task,
361
+ });
362
+ return;
363
+ case 'subagent_completed':
364
+ this.#log({ type: 'subagent_completed', result: event.result });
365
+ return;
366
+ case 'error':
367
+ this.#log({ type: 'error', message: event.message, ...(event.kind ? { kind: event.kind } : {}) });
368
+ return;
369
+ case 'final': {
370
+ const snapshot = this.getCurrentSnapshot();
371
+ this.#log({
372
+ type: 'assistant_final',
373
+ message: {
374
+ id: `bot-${snapshot.history.length}-${Date.now()}`,
375
+ sender: 'bot',
376
+ status: 'finalized',
377
+ text: event.finalText,
378
+ ...(event.reasoningText ? { reasoningText: event.reasoningText } : {}),
379
+ },
380
+ finalText: event.finalText,
381
+ ...(event.reasoningText ? { reasoningText: event.reasoningText } : {}),
382
+ ...(event.usage ? { usage: event.usage } : {}),
383
+ snapshot,
384
+ });
385
+ return;
386
+ }
387
+ default:
388
+ return;
389
+ }
390
+ }
391
+ #dedupeToolStarted(event) {
392
+ if (event.type !== 'tool_started') {
393
+ return event;
394
+ }
395
+ if (this.emittedToolStartedCallIds.has(event.toolCallId)) {
396
+ return null;
397
+ }
398
+ this.emittedToolStartedCallIds.add(event.toolCallId);
399
+ return event;
400
+ }
115
401
  exportState() {
116
402
  return {
117
403
  history: this.conversationStore.getHistory(),
118
404
  previousResponseId: this.previousResponseId,
405
+ toolLedger: this.toolLedger.export(),
119
406
  };
120
407
  }
121
408
  importState(state) {
122
409
  this.conversationStore.clear();
123
- for (const item of state.history) {
410
+ this.toolLedger.import(state.toolLedger);
411
+ const reconciled = reconcileHistoryWithToolLedger(state.history, state.toolLedger);
412
+ if (reconciled.addedCompletedPairs > 0 || reconciled.droppedIncompleteCalls > 0) {
413
+ this.logger.warn('Reconciled saved conversation history with tool execution ledger', {
414
+ eventType: 'conversation.tool_ledger.reconciled',
415
+ category: 'conversation',
416
+ phase: 'resume',
417
+ sessionId: this.id,
418
+ addedCompletedPairs: reconciled.addedCompletedPairs,
419
+ droppedIncompleteCalls: reconciled.droppedIncompleteCalls,
420
+ });
421
+ }
422
+ for (const item of reconciled.history) {
124
423
  this.conversationStore.addImportedItem(item);
125
424
  }
126
425
  this.previousResponseId = state.previousResponseId;
426
+ this.emittedToolStartedCallIds.clear();
127
427
  this.inputSurgeGuard.reset();
428
+ this.largeUncachedInputGuard.markResumedSession({
429
+ updatedAtMs: state.updatedAt ? Date.parse(state.updatedAt) : null,
430
+ });
128
431
  this.generation++;
129
432
  }
130
433
  addShellContext(historyText) {
@@ -150,6 +453,8 @@ export class ConversationSession {
150
453
  const turn = normalizeUserTurn(input);
151
454
  const text = turn.text;
152
455
  let addedUserMessage = false;
456
+ const ledgerSnapshot = this.toolLedger.export();
457
+ this.toolLedger.beginTurn();
153
458
  try {
154
459
  this.logger.debug('Conversation stream start', {
155
460
  eventType: 'stream.started',
@@ -165,6 +470,12 @@ export class ConversationSession {
165
470
  this.conversationStore.addUserTurn(turn);
166
471
  addedUserMessage = true;
167
472
  }
473
+ else if (abortedContext && !skipUserMessage) {
474
+ // The UI appended a user message for this input, but the store will consume it
475
+ // as fake tool output for the aborted approval rather than as a new user turn.
476
+ // Signal the UI to mark it so /undo skips it.
477
+ yield { type: 'user_message_consumed_for_abort' };
478
+ }
168
479
  // If there's an aborted approval, we need to resolve it first.
169
480
  // The user's message is a new input, but the agent is stuck waiting for tool output.
170
481
  if (abortedContext) {
@@ -182,16 +493,43 @@ export class ConversationSession {
182
493
  try {
183
494
  const continuedStream = (await this.agentClient.continueRunStream(abortedContext.state, {
184
495
  previousResponseId: this.previousResponseId,
496
+ sessionId: this.id,
185
497
  }));
186
498
  const acc = createStreamAccumulator();
187
499
  acc.emittedCommandIds = new Set(abortedContext.emittedCommandIds);
188
- yield* processStreamEvents(continuedStream, acc, {
500
+ for await (const event of processStreamEvents(continuedStream, acc, {
189
501
  toolCallArgumentsById: this.toolCallArgumentsById,
190
502
  emittedInvalidToolCallPackets: this.emittedInvalidToolCallPackets,
191
503
  preserveExistingToolArgs: true,
192
- }, { logger: this.logger, sessionId: this.id });
504
+ onFunctionCallItem: (item) => this.toolLedger.recordFunctionCall(item),
505
+ onFunctionResultItem: (item) => {
506
+ this.toolLedger.recordFunctionResult(item);
507
+ const cid = callIdOfLocal(item);
508
+ if (cid && this.logSink) {
509
+ const entry = this.toolLedger.export().find((e) => e.callId === cid);
510
+ this.#log({
511
+ type: 'tool_result',
512
+ callId: cid,
513
+ toolName: entry?.toolName ?? toolNameOfLocal(item),
514
+ status: entry?.status === 'failed' || entry?.status === 'aborted' ? entry.status : 'completed',
515
+ output: entry?.output ?? outputOfLocal(item),
516
+ ...(entry?.historyItems ? { historyItems: entry.historyItems } : {}),
517
+ });
518
+ }
519
+ },
520
+ }, { logger: this.logger, sessionId: this.id })) {
521
+ const filtered = this.#dedupeToolStarted(event);
522
+ if (filtered)
523
+ yield filtered;
524
+ }
193
525
  if (this.#isCurrentGeneration(gen)) {
194
526
  this.previousResponseId = continuedStream.lastResponseId ?? null;
527
+ warnIfStreamHistoryReplayedTools({
528
+ logger: this.logger,
529
+ sessionId: this.id,
530
+ source: 'abortResolution',
531
+ stream: continuedStream,
532
+ });
195
533
  this.conversationStore.updateFromResult(continuedStream);
196
534
  }
197
535
  // Check if another interruption occurred
@@ -220,49 +558,33 @@ export class ConversationSession {
220
558
  }
221
559
  }
222
560
  // Normal message flow
223
- const getProvider = getMethod(this.agentClient, 'getProvider');
224
- const provider = getProvider ? getProvider.call(this.agentClient) : 'openai';
225
- const supportsChaining = supportsConversationChaining(provider);
226
- const history = this.conversationStore.getHistory();
227
561
  // Use chaining mode only when the provider supports it AND either a valid
228
562
  // chain exists (previousResponseId is set) or there is no prior history to
229
563
  // resync (fresh start with just the current message). After undo the chain
230
564
  // is severed (previousResponseId = null) while prior turns remain in the
231
565
  // local store, so we fall back to full-history mode to re-establish context.
232
- const useChaining = supportsChaining && (!!this.previousResponseId || history.length <= 1);
233
- let streamInput;
566
+ const { streamInput, inputSurgeKind, modeNoticeToPersist } = this.#buildOutgoingInput(turn, {
567
+ includeTurn: false,
568
+ });
234
569
  if (this.pendingModeNotice) {
235
- const notice = this.pendingModeNotice;
236
570
  this.pendingModeNotice = null;
237
- if (useChaining) {
238
- const latestInput = history[history.length - 1] ?? text;
239
- const chainedInput = turn.images?.length ? latestInput : text;
240
- const userItem = typeof chainedInput === 'string' ? { role: 'user', type: 'message', content: chainedInput } : chainedInput;
241
- const noticeItem = { role: 'system', type: 'message', content: notice };
242
- streamInput = [noticeItem, userItem];
243
- }
244
- else {
245
- // Persist the notice at the tail (append-only) rather than splicing
246
- // it before the last user turn or sending it as a one-shot item.
247
- // Mid-history insertion adds a stray ephemeral cache breakpoint (see
248
- // addCacheControlToLastTwoMessages) and a transient item makes
249
- // consecutive requests diverge at the tail — both break the
250
- // Claude/Qwen prompt cache. Appending keeps the cached prefix
251
- // byte-identical and only growing.
252
- this.conversationStore.addModeNotice(notice);
253
- streamInput = this.conversationStore.getHistory();
254
- }
255
571
  }
256
- else {
257
- const latestInput = history[history.length - 1] ?? text;
258
- const chainedInput = turn.images?.length ? latestInput : text;
259
- streamInput = useChaining ? (typeof chainedInput === 'string' ? chainedInput : [chainedInput]) : history;
572
+ if (modeNoticeToPersist) {
573
+ // Persist the notice at the tail (append-only) rather than splicing
574
+ // it before the last user turn or sending it as a one-shot item.
575
+ // Mid-history insertion adds a stray ephemeral cache breakpoint (see
576
+ // addCacheControlToLastTwoMessages) and a transient item makes
577
+ // consecutive requests diverge at the tail — both break the
578
+ // Claude/Qwen prompt cache. Appending keeps the cached prefix
579
+ // byte-identical and only growing.
580
+ this.conversationStore.addModeNotice(modeNoticeToPersist);
260
581
  }
261
- const inputSurgeKind = useChaining ? 'delta' : 'full_history';
262
582
  const surgeDecision = this.inputSurgeGuard.inspect(streamInput, { kind: inputSurgeKind });
263
583
  if (surgeDecision.action === 'block') {
584
+ let droppedUserMessage;
264
585
  if (addedUserMessage && this.#isCurrentGeneration(gen)) {
265
586
  this.conversationStore.removeLastUserMessage();
587
+ droppedUserMessage = { text: turn.text, imageCount: turn.images?.length ?? 0 };
266
588
  }
267
589
  this.logger.warn('Input surge guard blocked provider request', {
268
590
  eventType: 'input_surge.blocked',
@@ -278,24 +600,38 @@ export class ConversationSession {
278
600
  type: 'error',
279
601
  kind: 'input_surge_guard',
280
602
  message: `${surgeDecision.reason} Request blocked to prevent runaway context growth. Try /undo or /clear, or compact the conversation history.`,
603
+ ...(droppedUserMessage ? { droppedUserMessage } : {}),
281
604
  };
282
605
  return;
283
606
  }
284
607
  stream = (await this.agentClient.startStream(streamInput, {
285
- previousResponseId: this.previousResponseId,
608
+ previousResponseId: inputSurgeKind === 'delta' ? this.previousResponseId : null,
609
+ sessionId: this.id,
286
610
  }));
287
611
  const acc = createStreamAccumulator();
288
- yield* processStreamEvents(stream, acc, {
612
+ for await (const event of processStreamEvents(stream, acc, {
289
613
  toolCallArgumentsById: this.toolCallArgumentsById,
290
614
  emittedInvalidToolCallPackets: this.emittedInvalidToolCallPackets,
291
615
  preserveExistingToolArgs: false,
292
- }, { logger: this.logger, sessionId: this.id });
616
+ onFunctionCallItem: (item) => this.toolLedger.recordFunctionCall(item),
617
+ onFunctionResultItem: (item) => this.toolLedger.recordFunctionResult(item),
618
+ }, { logger: this.logger, sessionId: this.id })) {
619
+ const filtered = this.#dedupeToolStarted(event);
620
+ if (filtered)
621
+ yield filtered;
622
+ }
293
623
  if (this.#isCurrentGeneration(gen)) {
294
624
  this.previousResponseId = stream.lastResponseId ?? null;
625
+ warnIfStreamHistoryReplayedTools({
626
+ logger: this.logger,
627
+ sessionId: this.id,
628
+ source: 'startStream',
629
+ stream,
630
+ });
295
631
  this.conversationStore.updateFromResult(stream);
296
632
  }
297
633
  const resolvedResult = yield* this.#buildAndResolve(stream, acc.finalOutput || undefined, acc.reasoningOutput || undefined, acc.emittedCommandIds, acc.latestUsage);
298
- if (useChaining) {
634
+ if (inputSurgeKind === 'delta') {
299
635
  this.inputSurgeGuard.recordSuccessfulInput(streamInput, { kind: inputSurgeKind });
300
636
  }
301
637
  else {
@@ -304,6 +640,14 @@ export class ConversationSession {
304
640
  previousInput: streamInput,
305
641
  });
306
642
  }
643
+ this.largeUncachedInputGuard.recordSuccessfulInput({
644
+ input: inputSurgeKind === 'delta' ? streamInput : this.conversationStore.getHistory(),
645
+ now: Date.now(),
646
+ provider: this.#getProviderForGuard(),
647
+ model: this.#getModelForGuard(),
648
+ reasoningEffort: this.#getReasoningEffortForGuard(),
649
+ mode: this.#getTrafficMode(),
650
+ });
307
651
  if (resolvedResult.type === 'approval_required') {
308
652
  this.logger.debug('Tool approval required', {
309
653
  eventType: 'approval.required',
@@ -343,6 +687,7 @@ export class ConversationSession {
343
687
  retryType: 'flex_service_tier',
344
688
  };
345
689
  getMethod(this.agentClient, 'useStandardServiceTierForNextRequest')?.call(this.agentClient);
690
+ this.toolLedger.import(ledgerSnapshot);
346
691
  yield* this.run(turn, {
347
692
  skipUserMessage: true,
348
693
  flexServiceTierFallbackCount: flexServiceTierFallbackCount + 1,
@@ -368,6 +713,7 @@ export class ConversationSession {
368
713
  if (!this.#isCurrentGeneration(gen))
369
714
  return;
370
715
  if (decision.hadStream && stream) {
716
+ this.toolLedger.import(ledgerSnapshot);
371
717
  if (decision.shouldInjectErrorContext) {
372
718
  this.conversationStore.addErrorContext(decision.errorContextMessage);
373
719
  }
@@ -378,20 +724,38 @@ export class ConversationSession {
378
724
  yield* this.run(turn, decision.nextRunOptions);
379
725
  return;
380
726
  }
381
- yield {
382
- type: 'error',
383
- message: error instanceof Error ? error.message : String(error),
384
- };
727
+ // Drop the just-added user turn from the store before yielding so the
728
+ // generator-cleanup path doesn't strand the removal — and so the error
729
+ // event can carry the dropped text for UI restoration.
730
+ let droppedUserMessage;
385
731
  if (addedUserMessage && !stream && this.#isCurrentGeneration(gen)) {
386
732
  this.conversationStore.removeLastUserMessage();
733
+ droppedUserMessage = { text: turn.text, imageCount: turn.images?.length ?? 0 };
734
+ }
735
+ if (stream && this.#isCurrentGeneration(gen)) {
736
+ this.toolLedger.markOpenCallsAborted(error instanceof Error ? error.message : String(error));
737
+ const recoverySummary = this.toolLedger.getRecoverySummary();
738
+ if (recoverySummary) {
739
+ yield {
740
+ type: 'tool_recovery',
741
+ recoveredCallIds: recoverySummary.recoveredCallIds,
742
+ droppedCallIds: recoverySummary.droppedCallIds,
743
+ message: recoverySummary.message,
744
+ };
745
+ }
387
746
  }
747
+ yield {
748
+ type: 'error',
749
+ message: describeError(error),
750
+ ...(droppedUserMessage ? { droppedUserMessage } : {}),
751
+ };
388
752
  this.logger.error('Conversation stream error', {
389
753
  eventType: 'stream.failed',
390
754
  category: 'stream',
391
755
  phase: 'abort',
392
756
  sessionId: this.id,
393
757
  traceId: this.logger.getCorrelationId(),
394
- errorMessage: error instanceof Error ? error.message : String(error),
758
+ errorMessage: describeError(error),
395
759
  });
396
760
  throw error;
397
761
  }
@@ -409,7 +773,9 @@ export class ConversationSession {
409
773
  }
410
774
  const { pendingApprovalContext: { state, toolCallArgumentsById, emittedCommandIds: previouslyEmittedIds }, toolStartedEvent, removeInterceptor, } = plan;
411
775
  if (toolStartedEvent) {
412
- yield toolStartedEvent;
776
+ const filtered = this.#dedupeToolStarted(toolStartedEvent);
777
+ if (filtered)
778
+ yield filtered;
413
779
  }
414
780
  // Restore cached tool-call arguments so continuation outputs can attach them
415
781
  this.toolCallArgumentsById.clear();
@@ -419,41 +785,103 @@ export class ConversationSession {
419
785
  }
420
786
  }
421
787
  try {
422
- const stream = (await this.agentClient.continueRunStream(state, {
423
- previousResponseId: this.previousResponseId,
424
- }));
425
- const acc = createStreamAccumulator();
426
- yield* processStreamEvents(stream, acc, {
427
- toolCallArgumentsById: this.toolCallArgumentsById,
428
- emittedInvalidToolCallPackets: this.emittedInvalidToolCallPackets,
429
- preserveExistingToolArgs: true,
430
- }, { logger: this.logger, sessionId: this.id });
431
- if (this.#isCurrentGeneration(gen)) {
432
- this.previousResponseId = stream.lastResponseId ?? null;
433
- this.conversationStore.updateFromResult(stream);
434
- }
435
- // Merge previously emitted command IDs with newly emitted ones
436
- // This prevents duplicates when result.history contains commands from the initial stream
437
- const allEmittedIds = new Set([...previouslyEmittedIds, ...acc.emittedCommandIds]);
438
- const resolvedResult = yield* this.#buildAndResolve(stream, acc.finalOutput || undefined, acc.reasoningOutput || undefined, allEmittedIds, acc.latestUsage);
439
- if (resolvedResult.type === 'approval_required') {
440
- this.logger.debug('Tool approval required', {
441
- eventType: 'approval.required',
442
- category: 'approval',
443
- phase: 'approval',
444
- sessionId: this.id,
445
- traceId: this.logger.getCorrelationId(),
446
- toolName: resolvedResult.approval.toolName,
447
- });
448
- yield toTerminalEvent(resolvedResult);
449
- return;
788
+ let transientRetryCount = 0;
789
+ while (true) {
790
+ try {
791
+ const stream = (await this.agentClient.continueRunStream(state, {
792
+ previousResponseId: this.previousResponseId,
793
+ sessionId: this.id,
794
+ }));
795
+ const acc = createStreamAccumulator();
796
+ for await (const event of processStreamEvents(stream, acc, {
797
+ toolCallArgumentsById: this.toolCallArgumentsById,
798
+ emittedInvalidToolCallPackets: this.emittedInvalidToolCallPackets,
799
+ preserveExistingToolArgs: true,
800
+ onFunctionCallItem: (item) => this.toolLedger.recordFunctionCall(item),
801
+ onFunctionResultItem: (item) => {
802
+ this.toolLedger.recordFunctionResult(item);
803
+ const cid = callIdOfLocal(item);
804
+ if (cid && this.logSink) {
805
+ const entry = this.toolLedger.export().find((e) => e.callId === cid);
806
+ this.#log({
807
+ type: 'tool_result',
808
+ callId: cid,
809
+ toolName: entry?.toolName ?? toolNameOfLocal(item),
810
+ status: entry?.status === 'failed' || entry?.status === 'aborted' ? entry.status : 'completed',
811
+ output: entry?.output ?? outputOfLocal(item),
812
+ ...(entry?.historyItems ? { historyItems: entry.historyItems } : {}),
813
+ });
814
+ }
815
+ },
816
+ }, { logger: this.logger, sessionId: this.id })) {
817
+ const filtered = this.#dedupeToolStarted(event);
818
+ if (filtered)
819
+ yield filtered;
820
+ }
821
+ if (this.#isCurrentGeneration(gen)) {
822
+ this.previousResponseId = stream.lastResponseId ?? null;
823
+ warnIfStreamHistoryReplayedTools({
824
+ logger: this.logger,
825
+ sessionId: this.id,
826
+ source: 'continueRunStream',
827
+ stream,
828
+ });
829
+ this.conversationStore.updateFromResult(stream);
830
+ }
831
+ // Merge previously emitted command IDs with newly emitted ones
832
+ // This prevents duplicates when result.history contains commands from the initial stream
833
+ const allEmittedIds = new Set([...previouslyEmittedIds, ...acc.emittedCommandIds]);
834
+ const resolvedResult = yield* this.#buildAndResolve(stream, acc.finalOutput || undefined, acc.reasoningOutput || undefined, allEmittedIds, acc.latestUsage);
835
+ if (resolvedResult.type === 'approval_required') {
836
+ this.logger.debug('Tool approval required', {
837
+ eventType: 'approval.required',
838
+ category: 'approval',
839
+ phase: 'approval',
840
+ sessionId: this.id,
841
+ traceId: this.logger.getCorrelationId(),
842
+ toolName: resolvedResult.approval.toolName,
843
+ });
844
+ yield toTerminalEvent(resolvedResult);
845
+ return;
846
+ }
847
+ yield toTerminalEvent(resolvedResult);
848
+ return;
849
+ }
850
+ catch (error) {
851
+ if (isTransientRetryableError(error) && transientRetryCount < MAX_TRANSIENT_RETRIES) {
852
+ transientRetryCount++;
853
+ const delay = Math.min(500 * Math.pow(2, transientRetryCount - 1), 30000);
854
+ this.logger.warn('Transient error in continuation, retrying', {
855
+ eventType: 'retry.transient',
856
+ category: 'retry',
857
+ phase: 'retry',
858
+ retryType: 'upstream',
859
+ retryAttempt: transientRetryCount,
860
+ maxRetries: MAX_TRANSIENT_RETRIES,
861
+ sessionId: this.id,
862
+ traceId: this.logger.getCorrelationId(),
863
+ errorMessage: error instanceof Error ? error.message : String(error),
864
+ delayMs: delay,
865
+ });
866
+ yield {
867
+ type: 'retry',
868
+ toolName: 'continuation',
869
+ attempt: transientRetryCount,
870
+ maxRetries: MAX_TRANSIENT_RETRIES,
871
+ errorMessage: error instanceof Error ? error.message : String(error),
872
+ retryType: 'upstream',
873
+ };
874
+ await new Promise((resolve) => setTimeout(resolve, delay));
875
+ continue;
876
+ }
877
+ throw error;
878
+ }
450
879
  }
451
- yield toTerminalEvent(resolvedResult);
452
880
  }
453
881
  catch (error) {
454
882
  yield {
455
883
  type: 'error',
456
- message: error instanceof Error ? error.message : String(error),
884
+ message: describeError(error),
457
885
  };
458
886
  throw error;
459
887
  }
@@ -463,69 +891,87 @@ export class ConversationSession {
463
891
  }
464
892
  }
465
893
  async sendMessage(input, { onTextChunk, onReasoningChunk, onCommandMessage, onEvent, hallucinationRetryCount = 0, } = {}) {
466
- getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, onEvent ?? null);
467
- let result;
468
- try {
469
- result = await collectTerminalResult(this.run(input, { hallucinationRetryCount }), {
470
- onTextChunk,
471
- onReasoningChunk,
472
- onCommandMessage,
473
- onEvent,
474
- getRawInterruption: () => this.approvalFlow.getPendingInterruption(),
475
- onFinalEvent: (event) => {
476
- this.logger.debug('sendMessage received final event', {
477
- sessionId: this.id,
478
- hasUsage: Boolean(event.usage),
479
- usage: event.usage,
480
- });
481
- },
482
- });
483
- }
484
- finally {
485
- getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, null);
486
- }
487
- if (result.type === 'response') {
488
- this.logger.debug('sendMessage returning response', {
489
- sessionId: this.id,
490
- hasUsage: Boolean(result.usage),
491
- usage: result.usage,
492
- });
493
- }
494
- return result;
894
+ const turn = normalizeUserTurn(input);
895
+ return this.#withTrafficContext(turn.text, async () => {
896
+ const wrappedOnEvent = (event) => {
897
+ this.#dispatchEventToLog(event);
898
+ onEvent?.(event);
899
+ };
900
+ getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, wrappedOnEvent);
901
+ let result;
902
+ try {
903
+ result = await collectTerminalResult(this.run(input, { hallucinationRetryCount }), {
904
+ onTextChunk,
905
+ onReasoningChunk,
906
+ onCommandMessage,
907
+ onEvent: wrappedOnEvent,
908
+ getRawInterruption: () => this.approvalFlow.getPendingInterruption(),
909
+ onFinalEvent: (event) => {
910
+ this.logger.debug('sendMessage received final event', {
911
+ sessionId: this.id,
912
+ hasUsage: Boolean(event.usage),
913
+ usage: event.usage,
914
+ });
915
+ },
916
+ });
917
+ }
918
+ finally {
919
+ getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, null);
920
+ }
921
+ if (result.type === 'response') {
922
+ this.logger.debug('sendMessage returning response', {
923
+ sessionId: this.id,
924
+ hasUsage: Boolean(result.usage),
925
+ usage: result.usage,
926
+ });
927
+ }
928
+ return result;
929
+ });
495
930
  }
496
931
  async handleApprovalDecision(answer, rejectionReason, { onTextChunk, onReasoningChunk, onCommandMessage, onEvent, } = {}) {
497
932
  if (!this.approvalFlow.getPending()) {
498
933
  return null;
499
934
  }
500
- getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, onEvent ?? null);
501
- let result;
502
- try {
503
- result = await collectTerminalResult(this['continue']({ answer, rejectionReason }), {
504
- onTextChunk,
505
- onReasoningChunk,
506
- onCommandMessage,
507
- onEvent,
508
- getRawInterruption: () => this.approvalFlow.getPendingInterruption(),
509
- onFinalEvent: (event) => {
510
- this.logger.debug('handleApprovalDecision received final event', {
511
- sessionId: this.id,
512
- hasUsage: Boolean(event.usage),
513
- usage: event.usage,
514
- });
515
- },
516
- });
517
- }
518
- finally {
519
- getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, null);
520
- }
521
- if (result.type === 'response') {
522
- this.logger.debug('handleApprovalDecision returning response', {
523
- sessionId: this.id,
524
- hasUsage: Boolean(result.usage),
525
- usage: result.usage,
526
- });
527
- }
528
- return result;
935
+ this.#log({
936
+ type: 'approval_resolved',
937
+ answer: answer === 'y' ? 'y' : 'n',
938
+ ...(rejectionReason ? { rejectionReason } : {}),
939
+ });
940
+ return this.#withTrafficContext(undefined, async () => {
941
+ const wrappedOnEvent = (event) => {
942
+ this.#dispatchEventToLog(event);
943
+ onEvent?.(event);
944
+ };
945
+ getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, wrappedOnEvent);
946
+ let result;
947
+ try {
948
+ result = await collectTerminalResult(this['continue']({ answer, rejectionReason }), {
949
+ onTextChunk,
950
+ onReasoningChunk,
951
+ onCommandMessage,
952
+ onEvent: wrappedOnEvent,
953
+ getRawInterruption: () => this.approvalFlow.getPendingInterruption(),
954
+ onFinalEvent: (event) => {
955
+ this.logger.debug('handleApprovalDecision received final event', {
956
+ sessionId: this.id,
957
+ hasUsage: Boolean(event.usage),
958
+ usage: event.usage,
959
+ });
960
+ },
961
+ });
962
+ }
963
+ finally {
964
+ getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, null);
965
+ }
966
+ if (result.type === 'response') {
967
+ this.logger.debug('handleApprovalDecision returning response', {
968
+ sessionId: this.id,
969
+ hasUsage: Boolean(result.usage),
970
+ usage: result.usage,
971
+ });
972
+ }
973
+ return result;
974
+ });
529
975
  }
530
976
  async *#buildAndResolve(result, finalOutputOverride, reasoningOutputOverride, emittedCommandIds, usage) {
531
977
  const outcome = await buildConversationResult({