@qduc/term2 0.6.1 → 0.7.1

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 (659) hide show
  1. package/dist/agent.d.ts +9 -1
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +158 -69
  4. package/dist/agent.js.map +1 -1
  5. package/dist/agent.test.js +165 -15
  6. package/dist/agent.test.js.map +1 -1
  7. package/dist/app.d.ts +14 -3
  8. package/dist/app.d.ts.map +1 -1
  9. package/dist/app.js +192 -28
  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.integration.test.d.ts +2 -0
  18. package/dist/cli.integration.test.d.ts.map +1 -0
  19. package/dist/cli.integration.test.js +102 -0
  20. package/dist/cli.integration.test.js.map +1 -0
  21. package/dist/cli.js +158 -71
  22. package/dist/cli.js.map +1 -1
  23. package/dist/commands/auto-approve-command.d.ts +10 -0
  24. package/dist/commands/auto-approve-command.d.ts.map +1 -0
  25. package/dist/commands/auto-approve-command.js +40 -0
  26. package/dist/commands/auto-approve-command.js.map +1 -0
  27. package/dist/commands/clear-command.d.ts +3 -0
  28. package/dist/commands/clear-command.d.ts.map +1 -0
  29. package/dist/commands/clear-command.js +12 -0
  30. package/dist/commands/clear-command.js.map +1 -0
  31. package/dist/commands/copy-command.d.ts +10 -0
  32. package/dist/commands/copy-command.d.ts.map +1 -0
  33. package/dist/commands/copy-command.js +25 -0
  34. package/dist/commands/copy-command.js.map +1 -0
  35. package/dist/commands/effort-command.d.ts +11 -0
  36. package/dist/commands/effort-command.d.ts.map +1 -0
  37. package/dist/commands/effort-command.js +34 -0
  38. package/dist/commands/effort-command.js.map +1 -0
  39. package/dist/commands/guarded-settings-command.d.ts +14 -0
  40. package/dist/commands/guarded-settings-command.d.ts.map +1 -0
  41. package/dist/commands/guarded-settings-command.js +23 -0
  42. package/dist/commands/guarded-settings-command.js.map +1 -0
  43. package/dist/commands/handoff-command.d.ts +10 -0
  44. package/dist/commands/handoff-command.d.ts.map +1 -0
  45. package/dist/commands/handoff-command.js +17 -0
  46. package/dist/commands/handoff-command.js.map +1 -0
  47. package/dist/commands/mode-commands.d.ts +32 -0
  48. package/dist/commands/mode-commands.d.ts.map +1 -0
  49. package/dist/commands/mode-commands.js +64 -0
  50. package/dist/commands/mode-commands.js.map +1 -0
  51. package/dist/commands/model-command.d.ts +11 -0
  52. package/dist/commands/model-command.d.ts.map +1 -0
  53. package/dist/commands/model-command.js +35 -0
  54. package/dist/commands/model-command.js.map +1 -0
  55. package/dist/commands/quit-command.d.ts +3 -0
  56. package/dist/commands/quit-command.d.ts.map +1 -0
  57. package/dist/commands/quit-command.js +10 -0
  58. package/dist/commands/quit-command.js.map +1 -0
  59. package/dist/commands/retry-command.d.ts +19 -0
  60. package/dist/commands/retry-command.d.ts.map +1 -0
  61. package/dist/commands/retry-command.js +30 -0
  62. package/dist/commands/retry-command.js.map +1 -0
  63. package/dist/commands/undo-command.d.ts +15 -0
  64. package/dist/commands/undo-command.d.ts.map +1 -0
  65. package/dist/commands/undo-command.js +28 -0
  66. package/dist/commands/undo-command.js.map +1 -0
  67. package/dist/commands/usage-command.d.ts +3 -0
  68. package/dist/commands/usage-command.d.ts.map +1 -0
  69. package/dist/commands/usage-command.js +11 -0
  70. package/dist/commands/usage-command.js.map +1 -0
  71. package/dist/components/Banner.d.ts.map +1 -1
  72. package/dist/components/Banner.js +35 -17
  73. package/dist/components/Banner.js.map +1 -1
  74. package/dist/components/BottomArea.d.ts +10 -1
  75. package/dist/components/BottomArea.d.ts.map +1 -1
  76. package/dist/components/BottomArea.js +8 -6
  77. package/dist/components/BottomArea.js.map +1 -1
  78. package/dist/components/BottomArea.test.js +14 -1
  79. package/dist/components/BottomArea.test.js.map +1 -1
  80. package/dist/components/CommandMessage.d.ts.map +1 -1
  81. package/dist/components/CommandMessage.js +31 -1
  82. package/dist/components/CommandMessage.js.map +1 -1
  83. package/dist/components/CommandMessage.test.js +150 -31
  84. package/dist/components/CommandMessage.test.js.map +1 -1
  85. package/dist/components/Common/MenuContainer.d.ts +20 -0
  86. package/dist/components/Common/MenuContainer.d.ts.map +1 -0
  87. package/dist/components/Common/MenuContainer.js +52 -0
  88. package/dist/components/Common/MenuContainer.js.map +1 -0
  89. package/dist/components/Common/ScrollableTabBar.d.ts +26 -0
  90. package/dist/components/Common/ScrollableTabBar.d.ts.map +1 -0
  91. package/dist/components/Common/ScrollableTabBar.js +26 -0
  92. package/dist/components/Common/ScrollableTabBar.js.map +1 -0
  93. package/dist/components/Common/compute-visible-tabs.d.ts +16 -0
  94. package/dist/components/Common/compute-visible-tabs.d.ts.map +1 -0
  95. package/dist/components/Common/compute-visible-tabs.js +53 -0
  96. package/dist/components/Common/compute-visible-tabs.js.map +1 -0
  97. package/dist/components/Common/compute-visible-tabs.test.d.ts +2 -0
  98. package/dist/components/Common/compute-visible-tabs.test.d.ts.map +1 -0
  99. package/dist/components/Common/compute-visible-tabs.test.js +97 -0
  100. package/dist/components/Common/compute-visible-tabs.test.js.map +1 -0
  101. package/dist/components/HandoffConfirmationPrompt.d.ts +9 -0
  102. package/dist/components/HandoffConfirmationPrompt.d.ts.map +1 -0
  103. package/dist/components/HandoffConfirmationPrompt.js +38 -0
  104. package/dist/components/HandoffConfirmationPrompt.js.map +1 -0
  105. package/dist/components/HandoffConfirmationPrompt.test.d.ts +2 -0
  106. package/dist/components/HandoffConfirmationPrompt.test.d.ts.map +1 -0
  107. package/dist/components/HandoffConfirmationPrompt.test.js +12 -0
  108. package/dist/components/HandoffConfirmationPrompt.test.js.map +1 -0
  109. package/dist/components/Input/PopupManager.d.ts +5 -1
  110. package/dist/components/Input/PopupManager.d.ts.map +1 -1
  111. package/dist/components/Input/PopupManager.js +2 -2
  112. package/dist/components/Input/PopupManager.js.map +1 -1
  113. package/dist/components/Input/popup-key-navigation.js +2 -2
  114. package/dist/components/Input/popup-key-navigation.js.map +1 -1
  115. package/dist/components/Input/popup-key-navigation.test.js +5 -3
  116. package/dist/components/Input/popup-key-navigation.test.js.map +1 -1
  117. package/dist/components/Input/popup-props.d.ts.map +1 -1
  118. package/dist/components/Input/popup-props.js +4 -0
  119. package/dist/components/Input/popup-props.js.map +1 -1
  120. package/dist/components/Input/popup-props.test.d.ts +2 -0
  121. package/dist/components/Input/popup-props.test.d.ts.map +1 -0
  122. package/dist/components/Input/popup-props.test.js +70 -0
  123. package/dist/components/Input/popup-props.test.js.map +1 -0
  124. package/dist/components/InputBox.d.ts +1 -1
  125. package/dist/components/InputBox.d.ts.map +1 -1
  126. package/dist/components/InputBox.js +67 -14
  127. package/dist/components/InputBox.js.map +1 -1
  128. package/dist/components/InputBox.test.js +192 -12
  129. package/dist/components/InputBox.test.js.map +1 -1
  130. package/dist/components/MessageList.d.ts +1 -0
  131. package/dist/components/MessageList.d.ts.map +1 -1
  132. package/dist/components/MessageList.js.map +1 -1
  133. package/dist/components/ModelSelectionMenu.d.ts.map +1 -1
  134. package/dist/components/ModelSelectionMenu.js +29 -125
  135. package/dist/components/ModelSelectionMenu.js.map +1 -1
  136. package/dist/components/ModelSelectionMenu.test.js +9 -5
  137. package/dist/components/ModelSelectionMenu.test.js.map +1 -1
  138. package/dist/components/PathSelectionMenu.d.ts.map +1 -1
  139. package/dist/components/PathSelectionMenu.js +15 -55
  140. package/dist/components/PathSelectionMenu.js.map +1 -1
  141. package/dist/components/SettingsSelectionMenu.d.ts +4 -1
  142. package/dist/components/SettingsSelectionMenu.d.ts.map +1 -1
  143. package/dist/components/SettingsSelectionMenu.js +48 -104
  144. package/dist/components/SettingsSelectionMenu.js.map +1 -1
  145. package/dist/components/SettingsSelectionMenu.scroll.test.js +140 -0
  146. package/dist/components/SettingsSelectionMenu.scroll.test.js.map +1 -1
  147. package/dist/components/SettingsSelectionMenu.test.js +38 -5
  148. package/dist/components/SettingsSelectionMenu.test.js.map +1 -1
  149. package/dist/components/SettingsValueSelectionMenu.d.ts.map +1 -1
  150. package/dist/components/SettingsValueSelectionMenu.js +24 -51
  151. package/dist/components/SettingsValueSelectionMenu.js.map +1 -1
  152. package/dist/components/SlashCommandMenu.d.ts +1 -0
  153. package/dist/components/SlashCommandMenu.d.ts.map +1 -1
  154. package/dist/components/SlashCommandMenu.js +11 -14
  155. package/dist/components/SlashCommandMenu.js.map +1 -1
  156. package/dist/components/StatusBar.d.ts +6 -0
  157. package/dist/components/StatusBar.d.ts.map +1 -1
  158. package/dist/components/StatusBar.js +51 -4
  159. package/dist/components/StatusBar.js.map +1 -1
  160. package/dist/components/StatusBar.test.js +47 -0
  161. package/dist/components/StatusBar.test.js.map +1 -1
  162. package/dist/components/UndoSelectionMenu.d.ts.map +1 -1
  163. package/dist/components/UndoSelectionMenu.js +6 -33
  164. package/dist/components/UndoSelectionMenu.js.map +1 -1
  165. package/dist/context/InputContext.stability.test.js +9 -7
  166. package/dist/context/InputContext.stability.test.js.map +1 -1
  167. package/dist/contracts/conversation.d.ts +3 -0
  168. package/dist/contracts/conversation.d.ts.map +1 -1
  169. package/dist/env-setup.js +1 -0
  170. package/dist/env-setup.js.map +1 -1
  171. package/dist/hooks/use-app-commands.d.ts +20 -21
  172. package/dist/hooks/use-app-commands.d.ts.map +1 -1
  173. package/dist/hooks/use-app-commands.js +65 -330
  174. package/dist/hooks/use-app-commands.js.map +1 -1
  175. package/dist/hooks/use-app-commands.test.js +146 -3
  176. package/dist/hooks/use-app-commands.test.js.map +1 -1
  177. package/dist/hooks/use-conversation.clear.test.js +61 -8
  178. package/dist/hooks/use-conversation.clear.test.js.map +1 -1
  179. package/dist/hooks/use-conversation.d.ts +25 -2
  180. package/dist/hooks/use-conversation.d.ts.map +1 -1
  181. package/dist/hooks/use-conversation.js +91 -30
  182. package/dist/hooks/use-conversation.js.map +1 -1
  183. package/dist/hooks/use-escape-key.d.ts +8 -1
  184. package/dist/hooks/use-escape-key.d.ts.map +1 -1
  185. package/dist/hooks/use-escape-key.js +15 -4
  186. package/dist/hooks/use-escape-key.js.map +1 -1
  187. package/dist/hooks/use-escape-key.test.js +135 -28
  188. package/dist/hooks/use-escape-key.test.js.map +1 -1
  189. package/dist/hooks/use-input-history.test.js +14 -8
  190. package/dist/hooks/use-input-history.test.js.map +1 -1
  191. package/dist/hooks/use-mode-handlers.d.ts +16 -2
  192. package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
  193. package/dist/hooks/use-mode-handlers.js +37 -2
  194. package/dist/hooks/use-mode-handlers.js.map +1 -1
  195. package/dist/hooks/use-model-selection.d.ts +6 -1
  196. package/dist/hooks/use-model-selection.d.ts.map +1 -1
  197. package/dist/hooks/use-model-selection.js +50 -8
  198. package/dist/hooks/use-model-selection.js.map +1 -1
  199. package/dist/hooks/use-model-selection.test.js +306 -50
  200. package/dist/hooks/use-model-selection.test.js.map +1 -1
  201. package/dist/hooks/use-path-completion.d.ts +4 -0
  202. package/dist/hooks/use-path-completion.d.ts.map +1 -1
  203. package/dist/hooks/use-path-completion.js +23 -17
  204. package/dist/hooks/use-path-completion.js.map +1 -1
  205. package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
  206. package/dist/hooks/use-runtime-settings.js +9 -4
  207. package/dist/hooks/use-runtime-settings.js.map +1 -1
  208. package/dist/hooks/use-runtime-settings.test.d.ts +2 -0
  209. package/dist/hooks/use-runtime-settings.test.d.ts.map +1 -0
  210. package/dist/hooks/use-runtime-settings.test.js +44 -0
  211. package/dist/hooks/use-runtime-settings.test.js.map +1 -0
  212. package/dist/hooks/use-selection.d.ts +6 -2
  213. package/dist/hooks/use-selection.d.ts.map +1 -1
  214. package/dist/hooks/use-selection.js +53 -16
  215. package/dist/hooks/use-selection.js.map +1 -1
  216. package/dist/hooks/use-selection.test.d.ts +2 -0
  217. package/dist/hooks/use-selection.test.d.ts.map +1 -0
  218. package/dist/hooks/use-selection.test.js +132 -0
  219. package/dist/hooks/use-selection.test.js.map +1 -0
  220. package/dist/hooks/use-settings-completion.d.ts +19 -0
  221. package/dist/hooks/use-settings-completion.d.ts.map +1 -1
  222. package/dist/hooks/use-settings-completion.js +106 -18
  223. package/dist/hooks/use-settings-completion.js.map +1 -1
  224. package/dist/hooks/use-settings-completion.scroll.test.js +68 -29
  225. package/dist/hooks/use-settings-completion.scroll.test.js.map +1 -1
  226. package/dist/hooks/use-settings-completion.test.js +36 -1
  227. package/dist/hooks/use-settings-completion.test.js.map +1 -1
  228. package/dist/hooks/use-settings-value-completion.d.ts +7 -1
  229. package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
  230. package/dist/hooks/use-settings-value-completion.js +18 -7
  231. package/dist/hooks/use-settings-value-completion.js.map +1 -1
  232. package/dist/hooks/use-slash-commands.d.ts +5 -0
  233. package/dist/hooks/use-slash-commands.d.ts.map +1 -1
  234. package/dist/hooks/use-slash-commands.js +32 -2
  235. package/dist/hooks/use-slash-commands.js.map +1 -1
  236. package/dist/hooks/use-slash-commands.test.js +35 -5
  237. package/dist/hooks/use-slash-commands.test.js.map +1 -1
  238. package/dist/hooks/use-trigger-detection.test.js +9 -6
  239. package/dist/hooks/use-trigger-detection.test.js.map +1 -1
  240. package/dist/hooks/use-undo-selection.d.ts +4 -0
  241. package/dist/hooks/use-undo-selection.d.ts.map +1 -1
  242. package/dist/hooks/use-undo-selection.js +20 -1
  243. package/dist/hooks/use-undo-selection.js.map +1 -1
  244. package/dist/lib/openai-agent-client.d.ts +10 -3
  245. package/dist/lib/openai-agent-client.d.ts.map +1 -1
  246. package/dist/lib/openai-agent-client.flex-tier.test.js +8 -3
  247. package/dist/lib/openai-agent-client.flex-tier.test.js.map +1 -1
  248. package/dist/lib/openai-agent-client.js +198 -38
  249. package/dist/lib/openai-agent-client.js.map +1 -1
  250. package/dist/lib/openai-agent-client.public-methods.test.js +193 -0
  251. package/dist/lib/openai-agent-client.public-methods.test.js.map +1 -1
  252. package/dist/lib/openai-agent-client.test.js +71 -1
  253. package/dist/lib/openai-agent-client.test.js.map +1 -1
  254. package/dist/lib/tool-invoke.d.ts +3 -2
  255. package/dist/lib/tool-invoke.d.ts.map +1 -1
  256. package/dist/lib/tool-invoke.js +213 -10
  257. package/dist/lib/tool-invoke.js.map +1 -1
  258. package/dist/lib/tool-invoke.test.js +103 -0
  259. package/dist/lib/tool-invoke.test.js.map +1 -1
  260. package/dist/lib/tool-selection-policy.d.ts.map +1 -1
  261. package/dist/lib/tool-selection-policy.js +43 -2
  262. package/dist/lib/tool-selection-policy.js.map +1 -1
  263. package/dist/lib/tool-selection-policy.test.js +39 -0
  264. package/dist/lib/tool-selection-policy.test.js.map +1 -1
  265. package/dist/prompts/codex.md +105 -97
  266. package/dist/prompts/fragments/gpt-5.3-codex.md +7 -0
  267. package/dist/prompts/fragments/gpt-5.4-small.md +6 -0
  268. package/dist/prompts/fragments/gpt-5.4.md +7 -0
  269. package/dist/prompts/fragments/gpt-5.5.md +7 -0
  270. package/dist/prompts/gpt-5-modern.md +90 -54
  271. package/dist/prompts/plan-mode-info.md +14 -3
  272. package/dist/prompts/prompt-constructor.d.ts +19 -0
  273. package/dist/prompts/prompt-constructor.d.ts.map +1 -0
  274. package/dist/prompts/prompt-constructor.js +53 -0
  275. package/dist/prompts/prompt-constructor.js.map +1 -0
  276. package/dist/prompts/prompt-constructor.test.d.ts +2 -0
  277. package/dist/prompts/prompt-constructor.test.d.ts.map +1 -0
  278. package/dist/prompts/prompt-constructor.test.js +99 -0
  279. package/dist/prompts/prompt-constructor.test.js.map +1 -0
  280. package/dist/prompts/prompt-constructor.test.ts +112 -0
  281. package/dist/prompts/prompt-constructor.ts +91 -0
  282. package/dist/prompts/prompt-profiles.d.ts +17 -0
  283. package/dist/prompts/prompt-profiles.d.ts.map +1 -0
  284. package/dist/prompts/prompt-profiles.js +61 -0
  285. package/dist/prompts/prompt-profiles.js.map +1 -0
  286. package/dist/prompts/prompt-profiles.ts +77 -0
  287. package/dist/prompts/prompt-selector.d.ts.map +1 -1
  288. package/dist/prompts/prompt-selector.js +3 -21
  289. package/dist/prompts/prompt-selector.js.map +1 -1
  290. package/dist/prompts/prompt-selector.ts +3 -25
  291. package/dist/prompts/reasoning-efficiency.d.ts.map +1 -1
  292. package/dist/prompts/reasoning-efficiency.js +2 -0
  293. package/dist/prompts/reasoning-efficiency.js.map +1 -1
  294. package/dist/prompts/reasoning-efficiency.test.d.ts +2 -0
  295. package/dist/prompts/reasoning-efficiency.test.d.ts.map +1 -0
  296. package/dist/prompts/reasoning-efficiency.test.js +11 -0
  297. package/dist/prompts/reasoning-efficiency.test.js.map +1 -0
  298. package/dist/prompts/reasoning-efficiency.test.ts +12 -0
  299. package/dist/prompts/reasoning-efficiency.ts +2 -0
  300. package/dist/prompts/search-via-shell.d.ts.map +1 -1
  301. package/dist/prompts/search-via-shell.js +0 -1
  302. package/dist/prompts/search-via-shell.js.map +1 -1
  303. package/dist/prompts/search-via-shell.test.js +19 -16
  304. package/dist/prompts/search-via-shell.test.js.map +1 -1
  305. package/dist/prompts/search-via-shell.test.ts +20 -16
  306. package/dist/prompts/search-via-shell.ts +0 -1
  307. package/dist/prompts/shell-auto-approval.d.ts +2 -2
  308. package/dist/prompts/shell-auto-approval.d.ts.map +1 -1
  309. package/dist/prompts/shell-auto-approval.js +6 -6
  310. package/dist/prompts/shell-auto-approval.js.map +1 -1
  311. package/dist/prompts/shell-auto-approval.ts +6 -6
  312. package/dist/prompts/simple-prev.md +55 -0
  313. package/dist/prompts/simple.md +31 -53
  314. package/dist/prompts/subagent-delegation.js +2 -2
  315. package/dist/prompts/subagent-delegation.js.map +1 -1
  316. package/dist/prompts/subagent-delegation.test.js +12 -10
  317. package/dist/prompts/subagent-delegation.test.js.map +1 -1
  318. package/dist/prompts/subagent-delegation.test.ts +13 -10
  319. package/dist/prompts/subagent-delegation.ts +2 -2
  320. package/dist/prompts/worktree-hygiene.md +9 -0
  321. package/dist/providers/ai-sdk-logging-fetch.d.ts.map +1 -1
  322. package/dist/providers/ai-sdk-logging-fetch.js +78 -3
  323. package/dist/providers/ai-sdk-logging-fetch.js.map +1 -1
  324. package/dist/providers/ai-sdk-logging-fetch.test.js +143 -0
  325. package/dist/providers/ai-sdk-logging-fetch.test.js.map +1 -1
  326. package/dist/providers/codex-responses-model.d.ts +21 -0
  327. package/dist/providers/codex-responses-model.d.ts.map +1 -0
  328. package/dist/providers/codex-responses-model.js +180 -0
  329. package/dist/providers/codex-responses-model.js.map +1 -0
  330. package/dist/providers/codex-responses-model.test.d.ts +2 -0
  331. package/dist/providers/codex-responses-model.test.d.ts.map +1 -0
  332. package/dist/providers/codex-responses-model.test.js +354 -0
  333. package/dist/providers/codex-responses-model.test.js.map +1 -0
  334. package/dist/providers/codex.provider.d.ts +27 -0
  335. package/dist/providers/codex.provider.d.ts.map +1 -0
  336. package/dist/providers/codex.provider.js +482 -0
  337. package/dist/providers/codex.provider.js.map +1 -0
  338. package/dist/providers/codex.provider.test.d.ts +2 -0
  339. package/dist/providers/codex.provider.test.d.ts.map +1 -0
  340. package/dist/providers/codex.provider.test.js +607 -0
  341. package/dist/providers/codex.provider.test.js.map +1 -0
  342. package/dist/providers/custom-provider-adapter.test.js +59 -3
  343. package/dist/providers/custom-provider-adapter.test.js.map +1 -1
  344. package/dist/providers/fallback-responses-model.d.ts +20 -0
  345. package/dist/providers/fallback-responses-model.d.ts.map +1 -0
  346. package/dist/providers/fallback-responses-model.js +346 -0
  347. package/dist/providers/fallback-responses-model.js.map +1 -0
  348. package/dist/providers/fallback-responses-model.test.d.ts +2 -0
  349. package/dist/providers/fallback-responses-model.test.d.ts.map +1 -0
  350. package/dist/providers/fallback-responses-model.test.js +568 -0
  351. package/dist/providers/fallback-responses-model.test.js.map +1 -0
  352. package/dist/providers/fetch/compose.d.ts +7 -0
  353. package/dist/providers/fetch/compose.d.ts.map +1 -0
  354. package/dist/providers/fetch/compose.js +20 -0
  355. package/dist/providers/fetch/compose.js.map +1 -0
  356. package/dist/providers/fetch/composer.d.ts +13 -0
  357. package/dist/providers/fetch/composer.d.ts.map +1 -0
  358. package/dist/providers/fetch/composer.js +15 -0
  359. package/dist/providers/fetch/composer.js.map +1 -0
  360. package/dist/providers/fetch/composer.test.d.ts +2 -0
  361. package/dist/providers/fetch/composer.test.d.ts.map +1 -0
  362. package/dist/providers/fetch/composer.test.js +448 -0
  363. package/dist/providers/fetch/composer.test.js.map +1 -0
  364. package/dist/providers/fetch/logging-middleware.d.ts +16 -0
  365. package/dist/providers/fetch/logging-middleware.d.ts.map +1 -0
  366. package/dist/providers/fetch/logging-middleware.js +214 -0
  367. package/dist/providers/fetch/logging-middleware.js.map +1 -0
  368. package/dist/providers/index.d.ts +1 -0
  369. package/dist/providers/index.d.ts.map +1 -1
  370. package/dist/providers/index.js +1 -0
  371. package/dist/providers/index.js.map +1 -1
  372. package/dist/providers/llama-cpp.provider.d.ts +3 -0
  373. package/dist/providers/llama-cpp.provider.d.ts.map +1 -0
  374. package/dist/providers/llama-cpp.provider.js +34 -0
  375. package/dist/providers/llama-cpp.provider.js.map +1 -0
  376. package/dist/providers/openai-compatible-middleware.d.ts +7 -0
  377. package/dist/providers/openai-compatible-middleware.d.ts.map +1 -0
  378. package/dist/providers/openai-compatible-middleware.js +133 -0
  379. package/dist/providers/openai-compatible-middleware.js.map +1 -0
  380. package/dist/providers/openai-compatible-models.d.ts +6 -0
  381. package/dist/providers/openai-compatible-models.d.ts.map +1 -0
  382. package/dist/providers/openai-compatible-models.js +17 -0
  383. package/dist/providers/openai-compatible-models.js.map +1 -0
  384. package/dist/providers/openai-compatible-response-normalizer.d.ts +7 -0
  385. package/dist/providers/openai-compatible-response-normalizer.d.ts.map +1 -0
  386. package/dist/providers/openai-compatible-response-normalizer.js +58 -0
  387. package/dist/providers/openai-compatible-response-normalizer.js.map +1 -0
  388. package/dist/providers/openai-compatible.provider.d.ts +6 -3
  389. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  390. package/dist/providers/openai-compatible.provider.js +71 -327
  391. package/dist/providers/openai-compatible.provider.js.map +1 -1
  392. package/dist/providers/openai-compatible.provider.test.js +106 -1
  393. package/dist/providers/openai-compatible.provider.test.js.map +1 -1
  394. package/dist/providers/openai.provider.js +44 -10
  395. package/dist/providers/openai.provider.js.map +1 -1
  396. package/dist/providers/opencode.provider.d.ts +19 -0
  397. package/dist/providers/opencode.provider.d.ts.map +1 -0
  398. package/dist/providers/opencode.provider.js +45 -0
  399. package/dist/providers/opencode.provider.js.map +1 -0
  400. package/dist/providers/openrouter.provider.d.ts +2 -3
  401. package/dist/providers/openrouter.provider.d.ts.map +1 -1
  402. package/dist/providers/openrouter.provider.js +23 -24
  403. package/dist/providers/openrouter.provider.js.map +1 -1
  404. package/dist/providers/openrouter.provider.request-preprocessing.test.js +12 -9
  405. package/dist/providers/openrouter.provider.request-preprocessing.test.js.map +1 -1
  406. package/dist/providers/protocol-client.d.ts +17 -0
  407. package/dist/providers/protocol-client.d.ts.map +1 -0
  408. package/dist/providers/protocol-client.js +57 -0
  409. package/dist/providers/protocol-client.js.map +1 -0
  410. package/dist/providers/protocol-client.test.d.ts +2 -0
  411. package/dist/providers/protocol-client.test.d.ts.map +1 -0
  412. package/dist/providers/protocol-client.test.js +55 -0
  413. package/dist/providers/protocol-client.test.js.map +1 -0
  414. package/dist/providers/registry.d.ts +1 -0
  415. package/dist/providers/registry.d.ts.map +1 -1
  416. package/dist/providers/registry.js.map +1 -1
  417. package/dist/providers/registry.test.js +6 -22
  418. package/dist/providers/registry.test.js.map +1 -1
  419. package/dist/providers/timed-responses-ws-model.d.ts +20 -0
  420. package/dist/providers/timed-responses-ws-model.d.ts.map +1 -0
  421. package/dist/providers/timed-responses-ws-model.js +381 -0
  422. package/dist/providers/timed-responses-ws-model.js.map +1 -0
  423. package/dist/providers/timed-responses-ws-model.test.d.ts +2 -0
  424. package/dist/providers/timed-responses-ws-model.test.d.ts.map +1 -0
  425. package/dist/providers/timed-responses-ws-model.test.js +441 -0
  426. package/dist/providers/timed-responses-ws-model.test.js.map +1 -0
  427. package/dist/providers/timed-ws-connection.d.ts +26 -0
  428. package/dist/providers/timed-ws-connection.d.ts.map +1 -0
  429. package/dist/providers/timed-ws-connection.js +156 -0
  430. package/dist/providers/timed-ws-connection.js.map +1 -0
  431. package/dist/providers/timed-ws-connection.test.d.ts +2 -0
  432. package/dist/providers/timed-ws-connection.test.d.ts.map +1 -0
  433. package/dist/providers/timed-ws-connection.test.js +223 -0
  434. package/dist/providers/timed-ws-connection.test.js.map +1 -0
  435. package/dist/providers/timed-ws-timeouts.d.ts +6 -0
  436. package/dist/providers/timed-ws-timeouts.d.ts.map +1 -0
  437. package/dist/providers/timed-ws-timeouts.js +6 -0
  438. package/dist/providers/timed-ws-timeouts.js.map +1 -0
  439. package/dist/scripts/eval-auto-approval/dataset.d.ts +2 -2
  440. package/dist/services/approval-flow-coordinator.d.ts.map +1 -1
  441. package/dist/services/approval-flow-coordinator.js +17 -1
  442. package/dist/services/approval-flow-coordinator.js.map +1 -1
  443. package/dist/services/approval-flow-coordinator.test.js +22 -0
  444. package/dist/services/approval-flow-coordinator.test.js.map +1 -1
  445. package/dist/services/command-message-streaming.d.ts.map +1 -1
  446. package/dist/services/command-message-streaming.js +8 -6
  447. package/dist/services/command-message-streaming.js.map +1 -1
  448. package/dist/services/command-message-streaming.test.js +4 -2
  449. package/dist/services/command-message-streaming.test.js.map +1 -1
  450. package/dist/services/conversation-clear-save.test.js +37 -66
  451. package/dist/services/conversation-clear-save.test.js.map +1 -1
  452. package/dist/services/conversation-events.d.ts +48 -1
  453. package/dist/services/conversation-events.d.ts.map +1 -1
  454. package/dist/services/conversation-history-repair.d.ts.map +1 -1
  455. package/dist/services/conversation-history-repair.js +81 -26
  456. package/dist/services/conversation-history-repair.js.map +1 -1
  457. package/dist/services/conversation-integration.test.js +112 -1
  458. package/dist/services/conversation-integration.test.js.map +1 -1
  459. package/dist/services/conversation-log-events.d.ts +113 -0
  460. package/dist/services/conversation-log-events.d.ts.map +1 -0
  461. package/dist/services/conversation-log-events.js +12 -0
  462. package/dist/services/conversation-log-events.js.map +1 -0
  463. package/dist/services/conversation-log-writer.d.ts +40 -0
  464. package/dist/services/conversation-log-writer.d.ts.map +1 -0
  465. package/dist/services/conversation-log-writer.js +264 -0
  466. package/dist/services/conversation-log-writer.js.map +1 -0
  467. package/dist/services/conversation-persistence-types.d.ts +46 -0
  468. package/dist/services/conversation-persistence-types.d.ts.map +1 -0
  469. package/dist/services/conversation-persistence-types.js +2 -0
  470. package/dist/services/conversation-persistence-types.js.map +1 -0
  471. package/dist/services/conversation-persistence.d.ts +44 -39
  472. package/dist/services/conversation-persistence.d.ts.map +1 -1
  473. package/dist/services/conversation-persistence.js +273 -83
  474. package/dist/services/conversation-persistence.js.map +1 -1
  475. package/dist/services/conversation-persistence.test.js +708 -624
  476. package/dist/services/conversation-persistence.test.js.map +1 -1
  477. package/dist/services/conversation-replay.d.ts +26 -0
  478. package/dist/services/conversation-replay.d.ts.map +1 -0
  479. package/dist/services/conversation-replay.js +460 -0
  480. package/dist/services/conversation-replay.js.map +1 -0
  481. package/dist/services/conversation-replay.test.d.ts +2 -0
  482. package/dist/services/conversation-replay.test.d.ts.map +1 -0
  483. package/dist/services/conversation-replay.test.js +492 -0
  484. package/dist/services/conversation-replay.test.js.map +1 -0
  485. package/dist/services/conversation-result-builder.d.ts +2 -0
  486. package/dist/services/conversation-result-builder.d.ts.map +1 -1
  487. package/dist/services/conversation-result-builder.js +12 -2
  488. package/dist/services/conversation-result-builder.js.map +1 -1
  489. package/dist/services/conversation-result-builder.test.js +179 -0
  490. package/dist/services/conversation-result-builder.test.js.map +1 -1
  491. package/dist/services/conversation-retry-policy.d.ts +17 -0
  492. package/dist/services/conversation-retry-policy.d.ts.map +1 -1
  493. package/dist/services/conversation-retry-policy.js +38 -5
  494. package/dist/services/conversation-retry-policy.js.map +1 -1
  495. package/dist/services/conversation-retry-policy.test.js +60 -1
  496. package/dist/services/conversation-retry-policy.test.js.map +1 -1
  497. package/dist/services/conversation-service.d.ts +12 -3
  498. package/dist/services/conversation-service.d.ts.map +1 -1
  499. package/dist/services/conversation-service.js +18 -3
  500. package/dist/services/conversation-service.js.map +1 -1
  501. package/dist/services/conversation-session.d.ts +23 -7
  502. package/dist/services/conversation-session.d.ts.map +1 -1
  503. package/dist/services/conversation-session.js +620 -67
  504. package/dist/services/conversation-session.js.map +1 -1
  505. package/dist/services/conversation-store.d.ts +16 -13
  506. package/dist/services/conversation-store.d.ts.map +1 -1
  507. package/dist/services/conversation-store.js +62 -191
  508. package/dist/services/conversation-store.js.map +1 -1
  509. package/dist/services/conversation-store.merge-modes.removed.d.ts +2 -0
  510. package/dist/services/conversation-store.merge-modes.removed.d.ts.map +1 -0
  511. package/dist/services/conversation-store.merge-modes.removed.js +2 -0
  512. package/dist/services/conversation-store.merge-modes.removed.js.map +1 -0
  513. package/dist/services/conversation-store.merge-modes.test.d.ts +2 -0
  514. package/dist/services/conversation-store.merge-modes.test.d.ts.map +1 -0
  515. package/dist/services/conversation-store.merge-modes.test.js +2 -0
  516. package/dist/services/conversation-store.merge-modes.test.js.map +1 -0
  517. package/dist/services/conversation-store.test.js +157 -294
  518. package/dist/services/conversation-store.test.js.map +1 -1
  519. package/dist/services/conversation-turn-items.d.ts +5 -0
  520. package/dist/services/conversation-turn-items.d.ts.map +1 -0
  521. package/dist/services/conversation-turn-items.js +351 -0
  522. package/dist/services/conversation-turn-items.js.map +1 -0
  523. package/dist/services/conversation-turn-items.test.d.ts +2 -0
  524. package/dist/services/conversation-turn-items.test.d.ts.map +1 -0
  525. package/dist/services/conversation-turn-items.test.js +68 -0
  526. package/dist/services/conversation-turn-items.test.js.map +1 -0
  527. package/dist/services/input-surge-guard.d.ts +4 -0
  528. package/dist/services/input-surge-guard.d.ts.map +1 -1
  529. package/dist/services/input-surge-guard.js +2 -2
  530. package/dist/services/input-surge-guard.js.map +1 -1
  531. package/dist/services/input-surge-guard.test.js +15 -1
  532. package/dist/services/input-surge-guard.test.js.map +1 -1
  533. package/dist/services/large-uncached-input-guard.d.ts +35 -0
  534. package/dist/services/large-uncached-input-guard.d.ts.map +1 -0
  535. package/dist/services/large-uncached-input-guard.js +127 -0
  536. package/dist/services/large-uncached-input-guard.js.map +1 -0
  537. package/dist/services/large-uncached-input-guard.test.d.ts +2 -0
  538. package/dist/services/large-uncached-input-guard.test.d.ts.map +1 -0
  539. package/dist/services/large-uncached-input-guard.test.js +160 -0
  540. package/dist/services/large-uncached-input-guard.test.js.map +1 -0
  541. package/dist/services/model-service.d.ts +2 -0
  542. package/dist/services/model-service.d.ts.map +1 -1
  543. package/dist/services/model-service.js +7 -0
  544. package/dist/services/model-service.js.map +1 -1
  545. package/dist/services/provider-traffic.d.ts.map +1 -1
  546. package/dist/services/provider-traffic.js +82 -24
  547. package/dist/services/provider-traffic.js.map +1 -1
  548. package/dist/services/provider-traffic.test.js +98 -6
  549. package/dist/services/provider-traffic.test.js.map +1 -1
  550. package/dist/services/repro-error.test.d.ts +2 -0
  551. package/dist/services/repro-error.test.d.ts.map +1 -0
  552. package/dist/services/repro-error.test.js +38 -0
  553. package/dist/services/repro-error.test.js.map +1 -0
  554. package/dist/services/rtk-service.d.ts.map +1 -1
  555. package/dist/services/rtk-service.js +1 -2
  556. package/dist/services/rtk-service.js.map +1 -1
  557. package/dist/services/settings-schema.d.ts +36 -28
  558. package/dist/services/settings-schema.d.ts.map +1 -1
  559. package/dist/services/settings-schema.js +7 -2
  560. package/dist/services/settings-schema.js.map +1 -1
  561. package/dist/services/settings-sources.d.ts.map +1 -1
  562. package/dist/services/settings-sources.js +1 -0
  563. package/dist/services/settings-sources.js.map +1 -1
  564. package/dist/services/stream-event-processor.d.ts +4 -0
  565. package/dist/services/stream-event-processor.d.ts.map +1 -1
  566. package/dist/services/stream-event-processor.js +78 -39
  567. package/dist/services/stream-event-processor.js.map +1 -1
  568. package/dist/services/stream-event-processor.test.js +71 -0
  569. package/dist/services/stream-event-processor.test.js.map +1 -1
  570. package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
  571. package/dist/services/subagents/subagent-manager.js +120 -46
  572. package/dist/services/subagents/subagent-manager.js.map +1 -1
  573. package/dist/services/subagents/subagent-manager.test.js +361 -11
  574. package/dist/services/subagents/subagent-manager.test.js.map +1 -1
  575. package/dist/services/subagents/subagent-session.d.ts +1 -1
  576. package/dist/services/subagents/subagent-session.d.ts.map +1 -1
  577. package/dist/services/subagents/subagent-session.js +2 -2
  578. package/dist/services/subagents/subagent-session.js.map +1 -1
  579. package/dist/services/subagents/subagent-session.test.js +3 -3
  580. package/dist/services/subagents/subagent-session.test.js.map +1 -1
  581. package/dist/services/subagents/types.d.ts +2 -1
  582. package/dist/services/subagents/types.d.ts.map +1 -1
  583. package/dist/services/terminal-result-collector.d.ts.map +1 -1
  584. package/dist/services/terminal-result-collector.js +49 -1
  585. package/dist/services/terminal-result-collector.js.map +1 -1
  586. package/dist/services/terminal-result-collector.test.js +44 -0
  587. package/dist/services/terminal-result-collector.test.js.map +1 -1
  588. package/dist/services/tool-call-arguments.d.ts +20 -0
  589. package/dist/services/tool-call-arguments.d.ts.map +1 -0
  590. package/dist/services/tool-call-arguments.js +49 -0
  591. package/dist/services/tool-call-arguments.js.map +1 -0
  592. package/dist/services/tool-execution-ledger.d.ts +41 -0
  593. package/dist/services/tool-execution-ledger.d.ts.map +1 -0
  594. package/dist/services/tool-execution-ledger.js +269 -0
  595. package/dist/services/tool-execution-ledger.js.map +1 -0
  596. package/dist/services/tool-execution-ledger.test.d.ts +2 -0
  597. package/dist/services/tool-execution-ledger.test.d.ts.map +1 -0
  598. package/dist/services/tool-execution-ledger.test.js +271 -0
  599. package/dist/services/tool-execution-ledger.test.js.map +1 -0
  600. package/dist/tools/apply-patch.d.ts +8 -0
  601. package/dist/tools/apply-patch.d.ts.map +1 -1
  602. package/dist/tools/apply-patch.js +187 -14
  603. package/dist/tools/apply-patch.js.map +1 -1
  604. package/dist/tools/apply-patch.test.js +81 -0
  605. package/dist/tools/apply-patch.test.js.map +1 -1
  606. package/dist/tools/edit-healing.d.ts.map +1 -1
  607. package/dist/tools/edit-healing.js +9 -3
  608. package/dist/tools/edit-healing.js.map +1 -1
  609. package/dist/tools/run-subagent.d.ts +0 -1
  610. package/dist/tools/run-subagent.d.ts.map +1 -1
  611. package/dist/tools/run-subagent.js +1 -8
  612. package/dist/tools/run-subagent.js.map +1 -1
  613. package/dist/tools/run-subagent.test.js +0 -18
  614. package/dist/tools/run-subagent.test.js.map +1 -1
  615. package/dist/tools/search-replace.d.ts.map +1 -1
  616. package/dist/tools/search-replace.js +92 -140
  617. package/dist/tools/search-replace.js.map +1 -1
  618. package/dist/utils/clipboard.d.ts +7 -0
  619. package/dist/utils/clipboard.d.ts.map +1 -1
  620. package/dist/utils/clipboard.js +83 -4
  621. package/dist/utils/clipboard.js.map +1 -1
  622. package/dist/utils/clipboard.test.js +152 -2
  623. package/dist/utils/clipboard.test.js.map +1 -1
  624. package/dist/utils/conversation-event-handler.d.ts.map +1 -1
  625. package/dist/utils/conversation-event-handler.js +36 -1
  626. package/dist/utils/conversation-event-handler.js.map +1 -1
  627. package/dist/utils/conversation-event-handler.test.js +116 -0
  628. package/dist/utils/conversation-event-handler.test.js.map +1 -1
  629. package/dist/utils/error-helpers.d.ts +1 -0
  630. package/dist/utils/error-helpers.d.ts.map +1 -1
  631. package/dist/utils/error-helpers.js +61 -0
  632. package/dist/utils/error-helpers.js.map +1 -1
  633. package/dist/utils/error-helpers.test.js +13 -1
  634. package/dist/utils/error-helpers.test.js.map +1 -1
  635. package/dist/utils/extract-command-messages.d.ts.map +1 -1
  636. package/dist/utils/extract-command-messages.js +8 -4
  637. package/dist/utils/extract-command-messages.js.map +1 -1
  638. package/dist/utils/ink-render-options.d.ts +1 -1
  639. package/dist/utils/ink-render-options.d.ts.map +1 -1
  640. package/dist/utils/ink-render-options.js +1 -1
  641. package/dist/utils/ink-render-options.js.map +1 -1
  642. package/dist/utils/message-utils.d.ts +7 -0
  643. package/dist/utils/message-utils.d.ts.map +1 -0
  644. package/dist/utils/message-utils.js +31 -0
  645. package/dist/utils/message-utils.js.map +1 -0
  646. package/dist/utils/resume-list.d.ts +15 -0
  647. package/dist/utils/resume-list.d.ts.map +1 -0
  648. package/dist/utils/resume-list.js +68 -0
  649. package/dist/utils/resume-list.js.map +1 -0
  650. package/dist/utils/resume-list.test.d.ts +2 -0
  651. package/dist/utils/resume-list.test.d.ts.map +1 -0
  652. package/dist/utils/resume-list.test.js +89 -0
  653. package/dist/utils/resume-list.test.js.map +1 -0
  654. package/dist/utils/streaming-session-factory.d.ts +2 -0
  655. package/dist/utils/streaming-session-factory.d.ts.map +1 -1
  656. package/dist/utils/streaming-session-factory.js +4 -0
  657. package/dist/utils/streaming-session-factory.js.map +1 -1
  658. package/package.json +2 -2
  659. package/readme.md +22 -7
@@ -3,17 +3,50 @@ import { decideRetry, isTransientRetryableError, MAX_HALLUCINATION_RETRIES, MAX_
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';
6
- import { getMethod } from './interruption-info.js';
6
+ import { getMethod, getCallIdFromObject } from './interruption-info.js';
7
7
  import { createStreamAccumulator, processStreamEvents } from './stream-event-processor.js';
8
8
  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, callIdOf, toolNameOf, outputOf, } from './tool-execution-ledger.js';
15
+ import { describeError } from '../utils/error-helpers.js';
13
16
  const supportsConversationChaining = (providerId) => {
14
17
  const providerDef = getProvider(providerId);
15
18
  return providerDef?.capabilities?.supportsConversationChaining ?? false;
16
19
  };
20
+ const asArray = (value) => (Array.isArray(value) ? value : []);
21
+ const warnIfStreamHistoryReplayedTools = ({ logger, sessionId, source, stream, }) => {
22
+ const streamRecord = stream;
23
+ const history = asArray(streamRecord.history);
24
+ const newItems = asArray(streamRecord.newItems);
25
+ const stateGeneratedItems = asArray(streamRecord.state?._generatedItems);
26
+ const historyDuplicates = collectDuplicateToolCallResultPairs(history);
27
+ const newItemsDuplicates = collectDuplicateToolCallResultPairs(newItems);
28
+ const stateGeneratedItemsDuplicates = collectDuplicateToolCallResultPairs(stateGeneratedItems);
29
+ if (historyDuplicates.pairs === 0 && newItemsDuplicates.pairs === 0 && stateGeneratedItemsDuplicates.pairs === 0) {
30
+ return;
31
+ }
32
+ logger.warn('Completed stream history contains replayed tool call/result pairs', {
33
+ eventType: 'conversation.stream_history.replayed_tools',
34
+ category: 'provider',
35
+ phase: 'post_stream',
36
+ sessionId,
37
+ traceId: logger.getCorrelationId(),
38
+ source,
39
+ historyLength: history.length,
40
+ newItemsLength: newItems.length,
41
+ stateGeneratedItemsLength: stateGeneratedItems.length,
42
+ historyDuplicatePairs: historyDuplicates.pairs,
43
+ historyMaxCopies: historyDuplicates.maxCopies,
44
+ newItemsDuplicatePairs: newItemsDuplicates.pairs,
45
+ newItemsMaxCopies: newItemsDuplicates.maxCopies,
46
+ stateGeneratedItemsDuplicatePairs: stateGeneratedItemsDuplicates.pairs,
47
+ stateGeneratedItemsMaxCopies: stateGeneratedItemsDuplicates.maxCopies,
48
+ });
49
+ };
17
50
  export class ConversationSession {
18
51
  id;
19
52
  startedAt;
@@ -24,12 +57,19 @@ export class ConversationSession {
24
57
  approvalState = new ApprovalState();
25
58
  toolCallArgumentsById = new Map();
26
59
  emittedInvalidToolCallPackets = new Set();
60
+ emittedToolStartedCallIds = new Set();
27
61
  shellAutoApproval;
28
62
  approvalFlow;
29
63
  inputSurgeGuard = new InputSurgeGuard();
64
+ largeUncachedInputGuard = new LargeUncachedInputGuard();
65
+ toolLedger = new ToolExecutionLedger();
30
66
  generation = 0;
67
+ #inputSurgeKind = 'delta';
68
+ currentPersistedTurnItems = [];
69
+ currentReasoningBuffer = '';
70
+ currentAssistantTextBuffer = '';
31
71
  settingsService;
32
- pendingModeNotice = null;
72
+ logSink = null;
33
73
  constructor(id, { agentClient, deps, sessionStartedAt, }) {
34
74
  this.id = id;
35
75
  this.startedAt = sessionStartedAt ?? new Date().toISOString();
@@ -63,6 +103,61 @@ export class ConversationSession {
63
103
  return 'mentor';
64
104
  return 'standard';
65
105
  }
106
+ #getModelForGuard() {
107
+ return this.settingsService?.get('agent.model') ?? null;
108
+ }
109
+ #getReasoningEffortForGuard() {
110
+ return this.settingsService?.get('agent.reasoningEffort') ?? null;
111
+ }
112
+ #getProviderForGuard() {
113
+ const getProvider = getMethod(this.agentClient, 'getProvider');
114
+ return getProvider
115
+ ? getProvider.call(this.agentClient)
116
+ : this.settingsService?.get('agent.provider') ?? null;
117
+ }
118
+ #makeUserInputItem(turn) {
119
+ const images = turn.images ?? [];
120
+ if (images.length === 0) {
121
+ return { role: 'user', type: 'message', content: turn.text ?? '' };
122
+ }
123
+ const content = [];
124
+ if (turn.text) {
125
+ content.push({ type: 'input_text', text: turn.text });
126
+ }
127
+ for (const image of images) {
128
+ content.push({
129
+ type: 'input_image',
130
+ image: `data:${image.mimeType};base64,${image.data}`,
131
+ detail: 'auto',
132
+ });
133
+ }
134
+ return { role: 'user', type: 'message', content };
135
+ }
136
+ #buildOutgoingInput(turn, { includeTurn }) {
137
+ const provider = this.#getProviderForGuard() ?? 'openai';
138
+ const supportsChaining = supportsConversationChaining(provider);
139
+ const history = this.#getCanonicalHistory();
140
+ const outgoingHistory = includeTurn ? [...history, this.#makeUserInputItem(turn)] : history;
141
+ const useChaining = supportsChaining && (!!this.previousResponseId || outgoingHistory.length <= 1);
142
+ const latestInput = outgoingHistory[outgoingHistory.length - 1] ?? turn.text;
143
+ const chainedInput = turn.images?.length ? latestInput : turn.text;
144
+ return {
145
+ streamInput: useChaining ? (typeof chainedInput === 'string' ? chainedInput : [chainedInput]) : outgoingHistory,
146
+ inputSurgeKind: useChaining ? 'delta' : 'full_history',
147
+ };
148
+ }
149
+ previewLargeUncachedInput(input, now = Date.now()) {
150
+ const turn = normalizeUserTurn(input);
151
+ const { streamInput } = this.#buildOutgoingInput(turn, { includeTurn: true });
152
+ return this.largeUncachedInputGuard.inspect({
153
+ input: streamInput,
154
+ now,
155
+ provider: this.#getProviderForGuard(),
156
+ model: this.#getModelForGuard(),
157
+ reasoningEffort: this.#getReasoningEffortForGuard(),
158
+ mode: this.#getTrafficMode(),
159
+ });
160
+ }
66
161
  #getFirstUserMessagePreview(currentTurn) {
67
162
  const [firstTurn] = this.conversationStore.listUserTurns();
68
163
  return firstTurn?.text ?? currentTurn ?? '';
@@ -89,8 +184,13 @@ export class ConversationSession {
89
184
  this.approvalState.clearPending();
90
185
  this.approvalState.consumeAborted();
91
186
  this.toolCallArgumentsById.clear();
187
+ this.emittedToolStartedCallIds.clear();
188
+ this.toolLedger = new ToolExecutionLedger();
92
189
  this.shellAutoApproval.clearCache();
93
190
  this.inputSurgeGuard.reset();
191
+ this.largeUncachedInputGuard.reset();
192
+ this.#inputSurgeKind = 'delta';
193
+ this.#resetPersistedTurnState();
94
194
  const clearConversations = getMethod(this.agentClient, 'clearConversations');
95
195
  clearConversations?.call(this.agentClient);
96
196
  }
@@ -103,8 +203,13 @@ export class ConversationSession {
103
203
  this.approvalState.clearPending();
104
204
  this.approvalState.consumeAborted();
105
205
  this.toolCallArgumentsById.clear();
206
+ this.emittedToolStartedCallIds.clear();
106
207
  this.shellAutoApproval.clearCache();
107
208
  this.inputSurgeGuard.reset();
209
+ this.largeUncachedInputGuard.markUndoOrRewind();
210
+ this.#inputSurgeKind = 'delta';
211
+ this.#resetPersistedTurnState();
212
+ this.#log({ type: 'undo', removedUserTurns: 1, snapshot: this.getCurrentSnapshot() });
108
213
  return removed;
109
214
  }
110
215
  listUserTurns() {
@@ -119,8 +224,13 @@ export class ConversationSession {
119
224
  this.approvalState.clearPending();
120
225
  this.approvalState.consumeAborted();
121
226
  this.toolCallArgumentsById.clear();
227
+ this.emittedToolStartedCallIds.clear();
122
228
  this.shellAutoApproval.clearCache();
123
229
  this.inputSurgeGuard.reset();
230
+ this.largeUncachedInputGuard.markUndoOrRewind();
231
+ this.#inputSurgeKind = 'delta';
232
+ this.#resetPersistedTurnState();
233
+ this.#log({ type: 'undo', removedUserTurns: n, snapshot: this.getCurrentSnapshot() });
124
234
  return removed;
125
235
  }
126
236
  setModel(model) {
@@ -135,52 +245,292 @@ export class ConversationSession {
135
245
  setTemperature?.call(this.agentClient, temperature);
136
246
  }
137
247
  setProvider(provider) {
248
+ this.generation++;
249
+ this.previousResponseId = null;
250
+ this.approvalState.clearPending();
251
+ this.approvalState.consumeAborted();
252
+ this.toolCallArgumentsById.clear();
253
+ this.emittedToolStartedCallIds.clear();
254
+ this.shellAutoApproval.clearCache();
255
+ this.inputSurgeGuard.reset();
256
+ const clearConversations = getMethod(this.agentClient, 'clearConversations');
257
+ clearConversations?.call(this.agentClient);
138
258
  const setProvider = getMethod(this.agentClient, 'setProvider');
139
259
  setProvider?.call(this.agentClient, provider);
140
260
  }
261
+ switchProvider(provider) {
262
+ this.setProvider(provider);
263
+ }
141
264
  setRetryCallback(callback) {
142
265
  if (typeof this.agentClient.setRetryCallback === 'function') {
143
266
  this.agentClient.setRetryCallback(callback);
144
267
  }
145
268
  }
269
+ setLogSink(sink) {
270
+ this.logSink = sink;
271
+ }
272
+ getCurrentSnapshot() {
273
+ const getProvider = getMethod(this.agentClient, 'getProvider');
274
+ const provider = getProvider
275
+ ? getProvider.call(this.agentClient)
276
+ : this.settingsService?.get('agent.provider');
277
+ const model = this.settingsService?.get('agent.model');
278
+ return {
279
+ history: this.#getCanonicalHistory(),
280
+ previousResponseId: this.previousResponseId,
281
+ toolLedger: this.toolLedger.export(),
282
+ ...(model ? { model } : {}),
283
+ ...(provider ? { provider } : {}),
284
+ };
285
+ }
286
+ #getCurrentAssistantTurnState() {
287
+ const getProvider = getMethod(this.agentClient, 'getProvider');
288
+ const provider = getProvider
289
+ ? getProvider.call(this.agentClient)
290
+ : this.settingsService?.get('agent.provider');
291
+ const model = this.settingsService?.get('agent.model');
292
+ return {
293
+ previousResponseId: this.previousResponseId,
294
+ ...(model ? { model } : {}),
295
+ ...(provider ? { provider } : {}),
296
+ };
297
+ }
298
+ #flushReasoningItem() {
299
+ if (this.currentReasoningBuffer) {
300
+ this.currentPersistedTurnItems.push({
301
+ type: 'reasoning',
302
+ text: this.currentReasoningBuffer,
303
+ });
304
+ this.currentReasoningBuffer = '';
305
+ }
306
+ }
307
+ #flushAssistantTextItem() {
308
+ if (this.currentAssistantTextBuffer) {
309
+ this.currentPersistedTurnItems.push({
310
+ type: 'assistant_text',
311
+ text: this.currentAssistantTextBuffer,
312
+ });
313
+ this.currentAssistantTextBuffer = '';
314
+ }
315
+ }
316
+ #recordToolCallItem(callId, toolName, args) {
317
+ this.#flushReasoningItem();
318
+ this.#flushAssistantTextItem();
319
+ this.currentPersistedTurnItems.push({
320
+ type: 'tool_call',
321
+ callId,
322
+ toolName,
323
+ arguments: args,
324
+ });
325
+ }
326
+ #recordToolResultItem(callId, toolName, status, output) {
327
+ this.#flushReasoningItem();
328
+ this.#flushAssistantTextItem();
329
+ this.currentPersistedTurnItems.push({
330
+ type: 'tool_result',
331
+ callId,
332
+ toolName,
333
+ status,
334
+ output,
335
+ });
336
+ }
337
+ #resetPersistedTurnState() {
338
+ this.currentPersistedTurnItems = [];
339
+ this.currentReasoningBuffer = '';
340
+ this.currentAssistantTextBuffer = '';
341
+ }
342
+ #log(event) {
343
+ if (!this.logSink)
344
+ return;
345
+ try {
346
+ this.logSink(event);
347
+ }
348
+ catch (err) {
349
+ this.logger.warn('Conversation log sink threw', {
350
+ eventType: 'conversation_log.sink_failed',
351
+ category: 'persistence',
352
+ errorMessage: err instanceof Error ? err.message : String(err),
353
+ });
354
+ }
355
+ }
356
+ #dispatchEventToLog(event) {
357
+ if (!this.logSink)
358
+ return;
359
+ switch (event.type) {
360
+ case 'text_delta':
361
+ if (this.currentReasoningBuffer) {
362
+ this.#flushReasoningItem();
363
+ }
364
+ this.currentAssistantTextBuffer += event.delta;
365
+ return;
366
+ case 'reasoning_delta':
367
+ if (this.currentAssistantTextBuffer) {
368
+ this.#flushAssistantTextItem();
369
+ }
370
+ this.currentReasoningBuffer += event.delta;
371
+ return;
372
+ case 'tool_started':
373
+ this.#recordToolCallItem(event.toolCallId, event.toolName, event.arguments);
374
+ this.#log({
375
+ type: 'tool_started',
376
+ toolCallId: event.toolCallId,
377
+ toolName: event.toolName,
378
+ arguments: event.arguments,
379
+ });
380
+ return;
381
+ case 'command_message':
382
+ this.#log({ type: 'command_message', message: event.message });
383
+ if (event.message.callId &&
384
+ event.message.toolName &&
385
+ (event.message.status === 'completed' || event.message.status === 'failed')) {
386
+ this.#recordToolResultItem(event.message.callId, event.message.toolName, event.message.status === 'failed' ? 'failed' : 'completed', event.message.output);
387
+ }
388
+ return;
389
+ case 'approval_required':
390
+ this.#log({
391
+ type: 'approval_required',
392
+ approval: {
393
+ toolName: event.approval.toolName,
394
+ argumentsText: event.approval.argumentsText,
395
+ agentName: event.approval.agentName,
396
+ ...('callId' in event.approval && event.approval.callId ? { callId: event.approval.callId } : {}),
397
+ },
398
+ });
399
+ return;
400
+ case 'subagent_started':
401
+ this.#log({
402
+ type: 'subagent_started',
403
+ agentId: event.agentId,
404
+ role: event.role,
405
+ task: event.task,
406
+ });
407
+ return;
408
+ case 'subagent_completed':
409
+ this.#log({ type: 'subagent_completed', result: event.result });
410
+ return;
411
+ case 'error':
412
+ this.#log({
413
+ type: 'error',
414
+ message: event.message,
415
+ ...(event.kind ? { kind: event.kind } : {}),
416
+ ...(event.stack ? { stack: event.stack } : {}),
417
+ });
418
+ return;
419
+ case 'final': {
420
+ const turnState = this.#getCurrentAssistantTurnState();
421
+ const toolLedger = this.toolLedger.export();
422
+ this.#flushReasoningItem();
423
+ this.#flushAssistantTextItem();
424
+ const turnItemsToLog = event.turnItems ? [...event.turnItems] : [...this.currentPersistedTurnItems];
425
+ if (event.finalText && !turnItemsToLog.some((item) => item.type === 'assistant_text')) {
426
+ turnItemsToLog.push({
427
+ type: 'assistant_text',
428
+ text: event.finalText,
429
+ });
430
+ }
431
+ // Invariant check: if transcript contains tool_result, matching call IDs exist in the live tool ledger.
432
+ for (const item of turnItemsToLog) {
433
+ if (item.type === 'tool_result') {
434
+ const exists = toolLedger.some((t) => t.callId === item.callId);
435
+ if (!exists) {
436
+ this.logger.warn(`Invariant violation: tool_result callId ${item.callId} not found in toolLedger`);
437
+ }
438
+ }
439
+ }
440
+ const turn = {
441
+ items: turnItemsToLog,
442
+ };
443
+ this.#log({
444
+ type: 'assistant_turn',
445
+ turn,
446
+ ...(event.usage ? { usage: event.usage } : {}),
447
+ state: turnState,
448
+ });
449
+ this.#resetPersistedTurnState();
450
+ return;
451
+ }
452
+ default:
453
+ return;
454
+ }
455
+ }
456
+ #dedupeToolStarted(event) {
457
+ if (event.type !== 'tool_started') {
458
+ return event;
459
+ }
460
+ if (this.emittedToolStartedCallIds.has(event.toolCallId)) {
461
+ return null;
462
+ }
463
+ this.emittedToolStartedCallIds.add(event.toolCallId);
464
+ return event;
465
+ }
466
+ #getCanonicalHistory() {
467
+ return reconcileHistoryWithToolLedger(this.conversationStore.getHistory(), this.toolLedger.export())
468
+ .history;
469
+ }
146
470
  exportState() {
147
471
  return {
148
- history: this.conversationStore.getHistory(),
472
+ history: this.#getCanonicalHistory(),
149
473
  previousResponseId: this.previousResponseId,
474
+ toolLedger: this.toolLedger.export(),
150
475
  };
151
476
  }
152
477
  importState(state) {
153
478
  this.conversationStore.clear();
154
- for (const item of state.history) {
479
+ this.toolLedger.import(state.toolLedger);
480
+ const reconciled = reconcileHistoryWithToolLedger(state.history, state.toolLedger);
481
+ if (reconciled.addedCompletedPairs > 0 || reconciled.droppedIncompleteCalls > 0) {
482
+ this.logger.warn('Reconciled saved conversation history with tool execution ledger', {
483
+ eventType: 'conversation.tool_ledger.reconciled',
484
+ category: 'conversation',
485
+ phase: 'resume',
486
+ sessionId: this.id,
487
+ addedCompletedPairs: reconciled.addedCompletedPairs,
488
+ droppedIncompleteCalls: reconciled.droppedIncompleteCalls,
489
+ });
490
+ }
491
+ for (const item of reconciled.history) {
155
492
  this.conversationStore.addImportedItem(item);
156
493
  }
157
494
  this.previousResponseId = state.previousResponseId;
495
+ this.emittedToolStartedCallIds.clear();
158
496
  this.inputSurgeGuard.reset();
497
+ this.largeUncachedInputGuard.markResumedSession({
498
+ updatedAtMs: state.updatedAt ? Date.parse(state.updatedAt) : null,
499
+ });
159
500
  this.generation++;
160
501
  }
161
502
  addShellContext(historyText) {
162
503
  this.conversationStore.addShellContext(historyText);
163
504
  }
164
- queueModeNotice(text) {
165
- this.pendingModeNotice = text;
166
- }
167
505
  /**
168
506
  * Abort the current running operation
169
507
  */
170
508
  abort() {
171
- this.approvalFlow.abort();
509
+ const pending = this.approvalFlow.getPending();
510
+ const callId = pending ? getCallIdFromObject(pending.interruption) : undefined;
511
+ if (this.approvalFlow.abort()) {
512
+ this.toolLedger.recordAbortedApproval('Tool execution was not approved.', 'Tool execution was not approved.', callId);
513
+ }
172
514
  }
173
515
  /**
174
516
  * Phase 4: stream conversation events as an async iterator.
175
517
  *
176
518
  * This is the transport-friendly primitive that can later be bridged to SSE/WebSockets.
177
519
  */
178
- async *run(input, { hallucinationRetryCount = 0, flexServiceTierFallbackCount = 0, skipUserMessage = false, } = {}) {
520
+ async *run(input, { skipUserMessage = false, flexServiceTierFallbackCount = 0, hallucinationRetryCount = 0, transientRetryCount = 0, } = {}) {
521
+ if (!skipUserMessage ||
522
+ hallucinationRetryCount > 0 ||
523
+ flexServiceTierFallbackCount > 0 ||
524
+ transientRetryCount > 0) {
525
+ this.#resetPersistedTurnState();
526
+ }
179
527
  const gen = this.generation;
180
528
  let stream = null;
181
529
  const turn = normalizeUserTurn(input);
182
530
  const text = turn.text;
183
531
  let addedUserMessage = false;
532
+ const ledgerSnapshot = this.toolLedger.export();
533
+ this.toolLedger.beginTurn();
184
534
  try {
185
535
  this.logger.debug('Conversation stream start', {
186
536
  eventType: 'stream.started',
@@ -196,6 +546,12 @@ export class ConversationSession {
196
546
  this.conversationStore.addUserTurn(turn);
197
547
  addedUserMessage = true;
198
548
  }
549
+ else if (abortedContext && !skipUserMessage) {
550
+ // The UI appended a user message for this input, but the store will consume it
551
+ // as fake tool output for the aborted approval rather than as a new user turn.
552
+ // Signal the UI to mark it so /undo skips it.
553
+ yield { type: 'user_message_consumed_for_abort' };
554
+ }
199
555
  // If there's an aborted approval, we need to resolve it first.
200
556
  // The user's message is a new input, but the agent is stuck waiting for tool output.
201
557
  if (abortedContext) {
@@ -213,17 +569,53 @@ export class ConversationSession {
213
569
  try {
214
570
  const continuedStream = (await this.agentClient.continueRunStream(abortedContext.state, {
215
571
  previousResponseId: this.previousResponseId,
572
+ sessionId: this.id,
216
573
  }));
217
574
  const acc = createStreamAccumulator();
218
575
  acc.emittedCommandIds = new Set(abortedContext.emittedCommandIds);
219
- yield* processStreamEvents(continuedStream, acc, {
576
+ for await (const event of processStreamEvents(continuedStream, acc, {
220
577
  toolCallArgumentsById: this.toolCallArgumentsById,
221
578
  emittedInvalidToolCallPackets: this.emittedInvalidToolCallPackets,
222
579
  preserveExistingToolArgs: true,
223
- }, { logger: this.logger, sessionId: this.id });
580
+ onFunctionCallItem: (item) => this.toolLedger.recordFunctionCall(item),
581
+ onFunctionResultItem: (item) => {
582
+ this.toolLedger.recordFunctionResult(item);
583
+ const cid = callIdOf(item);
584
+ if (cid && this.logSink) {
585
+ const entry = this.toolLedger.export().find((e) => e.callId === cid);
586
+ this.#log({
587
+ type: 'tool_result',
588
+ callId: cid,
589
+ toolName: entry?.toolName ?? toolNameOf(item),
590
+ status: entry?.status === 'failed' || entry?.status === 'aborted' ? entry.status : 'completed',
591
+ output: entry?.output ?? outputOf(item),
592
+ ...(entry?.historyItems ? { historyItems: entry.historyItems } : {}),
593
+ });
594
+ }
595
+ },
596
+ }, { logger: this.logger, sessionId: this.id })) {
597
+ const filtered = this.#dedupeToolStarted(event);
598
+ if (filtered)
599
+ yield filtered;
600
+ }
224
601
  if (this.#isCurrentGeneration(gen)) {
225
602
  this.previousResponseId = continuedStream.lastResponseId ?? null;
226
- this.conversationStore.updateFromResult(continuedStream);
603
+ warnIfStreamHistoryReplayedTools({
604
+ logger: this.logger,
605
+ sessionId: this.id,
606
+ source: 'abortResolution',
607
+ stream: continuedStream,
608
+ });
609
+ const s = continuedStream;
610
+ const terminal = !continuedStream.interruptions || continuedStream.interruptions.length === 0;
611
+ if (terminal) {
612
+ if (this.#inputSurgeKind === 'delta') {
613
+ this.conversationStore.appendOutput(s.output);
614
+ }
615
+ else {
616
+ this.conversationStore.replaceHistory(s.history);
617
+ }
618
+ }
227
619
  }
228
620
  // Check if another interruption occurred
229
621
  if (continuedStream.interruptions && continuedStream.interruptions.length > 0) {
@@ -251,49 +643,21 @@ export class ConversationSession {
251
643
  }
252
644
  }
253
645
  // Normal message flow
254
- const getProvider = getMethod(this.agentClient, 'getProvider');
255
- const provider = getProvider ? getProvider.call(this.agentClient) : 'openai';
256
- const supportsChaining = supportsConversationChaining(provider);
257
- const history = this.conversationStore.getHistory();
258
646
  // Use chaining mode only when the provider supports it AND either a valid
259
647
  // chain exists (previousResponseId is set) or there is no prior history to
260
648
  // resync (fresh start with just the current message). After undo the chain
261
649
  // is severed (previousResponseId = null) while prior turns remain in the
262
650
  // local store, so we fall back to full-history mode to re-establish context.
263
- const useChaining = supportsChaining && (!!this.previousResponseId || history.length <= 1);
264
- let streamInput;
265
- if (this.pendingModeNotice) {
266
- const notice = this.pendingModeNotice;
267
- this.pendingModeNotice = null;
268
- if (useChaining) {
269
- const latestInput = history[history.length - 1] ?? text;
270
- const chainedInput = turn.images?.length ? latestInput : text;
271
- const userItem = typeof chainedInput === 'string' ? { role: 'user', type: 'message', content: chainedInput } : chainedInput;
272
- const noticeItem = { role: 'system', type: 'message', content: notice };
273
- streamInput = [noticeItem, userItem];
274
- }
275
- else {
276
- // Persist the notice at the tail (append-only) rather than splicing
277
- // it before the last user turn or sending it as a one-shot item.
278
- // Mid-history insertion adds a stray ephemeral cache breakpoint (see
279
- // addCacheControlToLastTwoMessages) and a transient item makes
280
- // consecutive requests diverge at the tail — both break the
281
- // Claude/Qwen prompt cache. Appending keeps the cached prefix
282
- // byte-identical and only growing.
283
- this.conversationStore.addModeNotice(notice);
284
- streamInput = this.conversationStore.getHistory();
285
- }
286
- }
287
- else {
288
- const latestInput = history[history.length - 1] ?? text;
289
- const chainedInput = turn.images?.length ? latestInput : text;
290
- streamInput = useChaining ? (typeof chainedInput === 'string' ? chainedInput : [chainedInput]) : history;
291
- }
292
- const inputSurgeKind = useChaining ? 'delta' : 'full_history';
651
+ const { streamInput, inputSurgeKind } = this.#buildOutgoingInput(turn, {
652
+ includeTurn: false,
653
+ });
654
+ this.#inputSurgeKind = inputSurgeKind;
293
655
  const surgeDecision = this.inputSurgeGuard.inspect(streamInput, { kind: inputSurgeKind });
294
656
  if (surgeDecision.action === 'block') {
657
+ let droppedUserMessage;
295
658
  if (addedUserMessage && this.#isCurrentGeneration(gen)) {
296
659
  this.conversationStore.removeLastUserMessage();
660
+ droppedUserMessage = { text: turn.text, imageCount: turn.images?.length ?? 0 };
297
661
  }
298
662
  this.logger.warn('Input surge guard blocked provider request', {
299
663
  eventType: 'input_surge.blocked',
@@ -309,24 +673,47 @@ export class ConversationSession {
309
673
  type: 'error',
310
674
  kind: 'input_surge_guard',
311
675
  message: `${surgeDecision.reason} Request blocked to prevent runaway context growth. Try /undo or /clear, or compact the conversation history.`,
676
+ ...(droppedUserMessage ? { droppedUserMessage } : {}),
312
677
  };
313
678
  return;
314
679
  }
315
680
  stream = (await this.agentClient.startStream(streamInput, {
316
- previousResponseId: this.previousResponseId,
681
+ previousResponseId: inputSurgeKind === 'delta' ? this.previousResponseId : null,
682
+ sessionId: this.id,
317
683
  }));
318
684
  const acc = createStreamAccumulator();
319
- yield* processStreamEvents(stream, acc, {
685
+ for await (const event of processStreamEvents(stream, acc, {
320
686
  toolCallArgumentsById: this.toolCallArgumentsById,
321
687
  emittedInvalidToolCallPackets: this.emittedInvalidToolCallPackets,
322
688
  preserveExistingToolArgs: false,
323
- }, { logger: this.logger, sessionId: this.id });
689
+ onFunctionCallItem: (item) => this.toolLedger.recordFunctionCall(item),
690
+ onFunctionResultItem: (item) => this.toolLedger.recordFunctionResult(item),
691
+ }, { logger: this.logger, sessionId: this.id })) {
692
+ const filtered = this.#dedupeToolStarted(event);
693
+ if (filtered)
694
+ yield filtered;
695
+ }
324
696
  if (this.#isCurrentGeneration(gen)) {
325
697
  this.previousResponseId = stream.lastResponseId ?? null;
326
- this.conversationStore.updateFromResult(stream);
698
+ warnIfStreamHistoryReplayedTools({
699
+ logger: this.logger,
700
+ sessionId: this.id,
701
+ source: 'startStream',
702
+ stream,
703
+ });
704
+ const s = stream;
705
+ const terminal = !stream.interruptions || stream.interruptions.length === 0;
706
+ if (terminal) {
707
+ if (inputSurgeKind === 'delta') {
708
+ this.conversationStore.appendOutput(s.output);
709
+ }
710
+ else {
711
+ this.conversationStore.replaceHistory(s.history);
712
+ }
713
+ }
327
714
  }
328
715
  const resolvedResult = yield* this.#buildAndResolve(stream, acc.finalOutput || undefined, acc.reasoningOutput || undefined, acc.emittedCommandIds, acc.latestUsage);
329
- if (useChaining) {
716
+ if (inputSurgeKind === 'delta') {
330
717
  this.inputSurgeGuard.recordSuccessfulInput(streamInput, { kind: inputSurgeKind });
331
718
  }
332
719
  else {
@@ -335,7 +722,23 @@ export class ConversationSession {
335
722
  previousInput: streamInput,
336
723
  });
337
724
  }
725
+ this.largeUncachedInputGuard.recordSuccessfulInput({
726
+ input: inputSurgeKind === 'delta' ? streamInput : this.conversationStore.getHistory(),
727
+ now: Date.now(),
728
+ provider: this.#getProviderForGuard(),
729
+ model: this.#getModelForGuard(),
730
+ reasoningEffort: this.#getReasoningEffortForGuard(),
731
+ mode: this.#getTrafficMode(),
732
+ });
338
733
  if (resolvedResult.type === 'approval_required') {
734
+ if (resolvedResult.approval.callId) {
735
+ this.toolLedger.recordFunctionCall({
736
+ type: 'function_call',
737
+ callId: resolvedResult.approval.callId,
738
+ name: resolvedResult.approval.toolName,
739
+ arguments: resolvedResult.approval.argumentsText,
740
+ });
741
+ }
339
742
  this.logger.debug('Tool approval required', {
340
743
  eventType: 'approval.required',
341
744
  category: 'approval',
@@ -374,12 +777,52 @@ export class ConversationSession {
374
777
  retryType: 'flex_service_tier',
375
778
  };
376
779
  getMethod(this.agentClient, 'useStandardServiceTierForNextRequest')?.call(this.agentClient);
780
+ this.toolLedger.import(ledgerSnapshot);
377
781
  yield* this.run(turn, {
378
782
  skipUserMessage: true,
379
783
  flexServiceTierFallbackCount: flexServiceTierFallbackCount + 1,
380
784
  });
381
785
  return;
382
786
  }
787
+ if (isTransientRetryableError(error) && transientRetryCount < MAX_TRANSIENT_RETRIES) {
788
+ const attempt = transientRetryCount + 1;
789
+ const delay = Math.min(500 * Math.pow(2, attempt - 1), 30000);
790
+ this.logger.warn('Transient upstream error detected, retrying turn', {
791
+ eventType: 'retry.transient',
792
+ category: 'retry',
793
+ phase: 'retry',
794
+ retryType: 'upstream',
795
+ retryAttempt: attempt,
796
+ attempt,
797
+ maxRetries: MAX_TRANSIENT_RETRIES,
798
+ sessionId: this.id,
799
+ traceId: this.logger.getCorrelationId(),
800
+ errorMessage: error instanceof Error ? error.message : String(error),
801
+ delayMs: delay,
802
+ });
803
+ yield {
804
+ type: 'retry',
805
+ toolName: 'turn',
806
+ attempt,
807
+ maxRetries: MAX_TRANSIENT_RETRIES,
808
+ errorMessage: error instanceof Error ? error.message : String(error),
809
+ retryType: 'upstream',
810
+ };
811
+ if (!this.#isCurrentGeneration(gen))
812
+ return;
813
+ if (stream) {
814
+ this.toolLedger.import(ledgerSnapshot);
815
+ }
816
+ else {
817
+ this.conversationStore.removeLastUserMessage();
818
+ }
819
+ await new Promise((resolve) => setTimeout(resolve, delay));
820
+ yield* this.run(turn, {
821
+ skipUserMessage: Boolean(stream),
822
+ transientRetryCount: attempt,
823
+ });
824
+ return;
825
+ }
383
826
  const decision = decideRetry(error, hallucinationRetryCount, Boolean(stream), streamHistoryLength);
384
827
  if (decision.kind === 'retry') {
385
828
  this.logger.warn('Recoverable model error detected, retrying', {
@@ -399,6 +842,7 @@ export class ConversationSession {
399
842
  if (!this.#isCurrentGeneration(gen))
400
843
  return;
401
844
  if (decision.hadStream && stream) {
845
+ this.toolLedger.import(ledgerSnapshot);
402
846
  if (decision.shouldInjectErrorContext) {
403
847
  this.conversationStore.addErrorContext(decision.errorContextMessage);
404
848
  }
@@ -409,20 +853,47 @@ export class ConversationSession {
409
853
  yield* this.run(turn, decision.nextRunOptions);
410
854
  return;
411
855
  }
412
- yield {
413
- type: 'error',
414
- message: error instanceof Error ? error.message : String(error),
415
- };
856
+ // Drop the just-added user turn from the store before yielding so the
857
+ // generator-cleanup path doesn't strand the removal — and so the error
858
+ // event can carry the dropped text for UI restoration.
859
+ let droppedUserMessage;
416
860
  if (addedUserMessage && !stream && this.#isCurrentGeneration(gen)) {
417
861
  this.conversationStore.removeLastUserMessage();
862
+ droppedUserMessage = { text: turn.text, imageCount: turn.images?.length ?? 0 };
863
+ }
864
+ if (stream && this.#isCurrentGeneration(gen)) {
865
+ this.toolLedger.markOpenCallsAborted(error instanceof Error ? error.message : String(error));
866
+ // Persist completed tool call/result pairs (and the recovery notice) into
867
+ // canonical history so the next user turn doesn't ship a request with two
868
+ // consecutive user messages and no record of the tool activity.
869
+ const reconciled = reconcileHistoryWithToolLedger(this.conversationStore.getHistory(), this.toolLedger.export());
870
+ if (reconciled.addedCompletedPairs > 0 || reconciled.droppedIncompleteCalls > 0) {
871
+ this.conversationStore.replaceHistory(reconciled.history);
872
+ }
873
+ const recoverySummary = this.toolLedger.getRecoverySummary();
874
+ if (recoverySummary) {
875
+ yield {
876
+ type: 'tool_recovery',
877
+ recoveredCallIds: recoverySummary.recoveredCallIds,
878
+ droppedCallIds: recoverySummary.droppedCallIds,
879
+ message: recoverySummary.message,
880
+ };
881
+ }
418
882
  }
883
+ yield {
884
+ type: 'error',
885
+ message: describeError(error),
886
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
887
+ ...(droppedUserMessage ? { droppedUserMessage } : {}),
888
+ };
419
889
  this.logger.error('Conversation stream error', {
420
890
  eventType: 'stream.failed',
421
891
  category: 'stream',
422
892
  phase: 'abort',
423
893
  sessionId: this.id,
424
894
  traceId: this.logger.getCorrelationId(),
425
- errorMessage: error instanceof Error ? error.message : String(error),
895
+ errorMessage: describeError(error),
896
+ stack: error instanceof Error ? error.stack : undefined,
426
897
  });
427
898
  throw error;
428
899
  }
@@ -438,9 +909,19 @@ export class ConversationSession {
438
909
  if (!plan) {
439
910
  return;
440
911
  }
912
+ if (answer !== 'y') {
913
+ const interruption = plan.pendingApprovalContext.interruption;
914
+ const callId = getCallIdFromObject(interruption);
915
+ const output = rejectionReason
916
+ ? `Tool execution was not approved. User's reason: ${rejectionReason}`
917
+ : 'Tool execution was not approved.';
918
+ this.toolLedger.recordAbortedApproval(output, output, callId);
919
+ }
441
920
  const { pendingApprovalContext: { state, toolCallArgumentsById, emittedCommandIds: previouslyEmittedIds }, toolStartedEvent, removeInterceptor, } = plan;
442
921
  if (toolStartedEvent) {
443
- yield toolStartedEvent;
922
+ const filtered = this.#dedupeToolStarted(toolStartedEvent);
923
+ if (filtered)
924
+ yield filtered;
444
925
  }
445
926
  // Restore cached tool-call arguments so continuation outputs can attach them
446
927
  this.toolCallArgumentsById.clear();
@@ -455,22 +936,66 @@ export class ConversationSession {
455
936
  try {
456
937
  const stream = (await this.agentClient.continueRunStream(state, {
457
938
  previousResponseId: this.previousResponseId,
939
+ sessionId: this.id,
458
940
  }));
459
941
  const acc = createStreamAccumulator();
460
- yield* processStreamEvents(stream, acc, {
942
+ for await (const event of processStreamEvents(stream, acc, {
461
943
  toolCallArgumentsById: this.toolCallArgumentsById,
462
944
  emittedInvalidToolCallPackets: this.emittedInvalidToolCallPackets,
463
945
  preserveExistingToolArgs: true,
464
- }, { logger: this.logger, sessionId: this.id });
946
+ onFunctionCallItem: (item) => this.toolLedger.recordFunctionCall(item),
947
+ onFunctionResultItem: (item) => {
948
+ this.toolLedger.recordFunctionResult(item);
949
+ const cid = callIdOf(item);
950
+ if (cid && this.logSink) {
951
+ const entry = this.toolLedger.export().find((e) => e.callId === cid);
952
+ this.#log({
953
+ type: 'tool_result',
954
+ callId: cid,
955
+ toolName: entry?.toolName ?? toolNameOf(item),
956
+ status: entry?.status === 'failed' || entry?.status === 'aborted' ? entry.status : 'completed',
957
+ output: entry?.output ?? outputOf(item),
958
+ ...(entry?.historyItems ? { historyItems: entry.historyItems } : {}),
959
+ });
960
+ }
961
+ },
962
+ }, { logger: this.logger, sessionId: this.id })) {
963
+ const filtered = this.#dedupeToolStarted(event);
964
+ if (filtered)
965
+ yield filtered;
966
+ }
465
967
  if (this.#isCurrentGeneration(gen)) {
466
968
  this.previousResponseId = stream.lastResponseId ?? null;
467
- this.conversationStore.updateFromResult(stream);
969
+ warnIfStreamHistoryReplayedTools({
970
+ logger: this.logger,
971
+ sessionId: this.id,
972
+ source: 'continueRunStream',
973
+ stream,
974
+ });
975
+ const s = stream;
976
+ const terminal = !stream.interruptions || stream.interruptions.length === 0;
977
+ if (terminal) {
978
+ if (this.#inputSurgeKind === 'delta') {
979
+ this.conversationStore.appendOutput(s.output);
980
+ }
981
+ else {
982
+ this.conversationStore.replaceHistory(s.history);
983
+ }
984
+ }
468
985
  }
469
986
  // Merge previously emitted command IDs with newly emitted ones
470
987
  // This prevents duplicates when result.history contains commands from the initial stream
471
988
  const allEmittedIds = new Set([...previouslyEmittedIds, ...acc.emittedCommandIds]);
472
989
  const resolvedResult = yield* this.#buildAndResolve(stream, acc.finalOutput || undefined, acc.reasoningOutput || undefined, allEmittedIds, acc.latestUsage);
473
990
  if (resolvedResult.type === 'approval_required') {
991
+ if (resolvedResult.approval.callId) {
992
+ this.toolLedger.recordFunctionCall({
993
+ type: 'function_call',
994
+ callId: resolvedResult.approval.callId,
995
+ name: resolvedResult.approval.toolName,
996
+ arguments: resolvedResult.approval.argumentsText,
997
+ });
998
+ }
474
999
  this.logger.debug('Tool approval required', {
475
1000
  eventType: 'approval.required',
476
1001
  category: 'approval',
@@ -517,9 +1042,19 @@ export class ConversationSession {
517
1042
  }
518
1043
  }
519
1044
  catch (error) {
1045
+ this.logger.error('Conversation stream error during continuation', {
1046
+ eventType: 'stream.failed',
1047
+ category: 'stream',
1048
+ phase: 'abort',
1049
+ sessionId: this.id,
1050
+ traceId: this.logger.getCorrelationId(),
1051
+ errorMessage: describeError(error),
1052
+ stack: error instanceof Error ? error.stack : undefined,
1053
+ });
520
1054
  yield {
521
1055
  type: 'error',
522
- message: error instanceof Error ? error.message : String(error),
1056
+ message: describeError(error),
1057
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
523
1058
  };
524
1059
  throw error;
525
1060
  }
@@ -531,14 +1066,18 @@ export class ConversationSession {
531
1066
  async sendMessage(input, { onTextChunk, onReasoningChunk, onCommandMessage, onEvent, hallucinationRetryCount = 0, } = {}) {
532
1067
  const turn = normalizeUserTurn(input);
533
1068
  return this.#withTrafficContext(turn.text, async () => {
534
- getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, onEvent ?? null);
1069
+ const wrappedOnEvent = (event) => {
1070
+ this.#dispatchEventToLog(event);
1071
+ onEvent?.(event);
1072
+ };
1073
+ getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, wrappedOnEvent);
535
1074
  let result;
536
1075
  try {
537
1076
  result = await collectTerminalResult(this.run(input, { hallucinationRetryCount }), {
538
1077
  onTextChunk,
539
1078
  onReasoningChunk,
540
1079
  onCommandMessage,
541
- onEvent,
1080
+ onEvent: wrappedOnEvent,
542
1081
  getRawInterruption: () => this.approvalFlow.getPendingInterruption(),
543
1082
  onFinalEvent: (event) => {
544
1083
  this.logger.debug('sendMessage received final event', {
@@ -566,15 +1105,24 @@ export class ConversationSession {
566
1105
  if (!this.approvalFlow.getPending()) {
567
1106
  return null;
568
1107
  }
1108
+ this.#log({
1109
+ type: 'approval_resolved',
1110
+ answer: answer === 'y' ? 'y' : 'n',
1111
+ ...(rejectionReason ? { rejectionReason } : {}),
1112
+ });
569
1113
  return this.#withTrafficContext(undefined, async () => {
570
- getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, onEvent ?? null);
1114
+ const wrappedOnEvent = (event) => {
1115
+ this.#dispatchEventToLog(event);
1116
+ onEvent?.(event);
1117
+ };
1118
+ getMethod(this.agentClient, 'setSubagentEventSink')?.call(this.agentClient, wrappedOnEvent);
571
1119
  let result;
572
1120
  try {
573
1121
  result = await collectTerminalResult(this['continue']({ answer, rejectionReason }), {
574
1122
  onTextChunk,
575
1123
  onReasoningChunk,
576
1124
  onCommandMessage,
577
- onEvent,
1125
+ onEvent: wrappedOnEvent,
578
1126
  getRawInterruption: () => this.approvalFlow.getPendingInterruption(),
579
1127
  onFinalEvent: (event) => {
580
1128
  this.logger.debug('handleApprovalDecision received final event', {
@@ -606,6 +1154,7 @@ export class ConversationSession {
606
1154
  emittedCommandIds,
607
1155
  usage,
608
1156
  toolCallArgumentsById: this.toolCallArgumentsById,
1157
+ turnItems: this.currentPersistedTurnItems,
609
1158
  }, {
610
1159
  approvalFlow: this.approvalFlow,
611
1160
  shellAutoApproval: this.shellAutoApproval,
@@ -621,6 +1170,7 @@ export class ConversationSession {
621
1170
  let continuationApprovalUsage;
622
1171
  const commandMessages = [];
623
1172
  let approvalRequiredResult;
1173
+ let continuationTurnItems;
624
1174
  for await (const event of this['continue']({ answer: 'y' })) {
625
1175
  if (event.type === 'approval_required') {
626
1176
  continuationApprovalUsage = event.usage;
@@ -649,7 +1199,9 @@ export class ConversationSession {
649
1199
  if (event.commandMessages) {
650
1200
  commandMessages.push(...event.commandMessages);
651
1201
  }
652
- yield event;
1202
+ if (event.turnItems) {
1203
+ continuationTurnItems = event.turnItems;
1204
+ }
653
1205
  }
654
1206
  else {
655
1207
  yield event;
@@ -669,6 +1221,7 @@ export class ConversationSession {
669
1221
  finalText: finalText || 'Done.',
670
1222
  ...(reasoningText ? { reasoningText } : {}),
671
1223
  ...(combinedUsage && Object.keys(combinedUsage).length > 0 ? { usage: combinedUsage } : {}),
1224
+ turnItems: continuationTurnItems ?? this.currentPersistedTurnItems,
672
1225
  };
673
1226
  }
674
1227
  }