@qduc/term2 0.6.1 → 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 (798) hide show
  1. package/dist/agent.d.ts +9 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +138 -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 +168 -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.js +139 -69
  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.js +12 -7
  127. package/dist/hooks/use-conversation.clear.test.js.map +1 -1
  128. package/dist/hooks/use-conversation.d.ts +21 -1
  129. package/dist/hooks/use-conversation.d.ts.map +1 -1
  130. package/dist/hooks/use-conversation.js +81 -9
  131. package/dist/hooks/use-conversation.js.map +1 -1
  132. package/dist/hooks/use-escape-key.d.ts +8 -1
  133. package/dist/hooks/use-escape-key.d.ts.map +1 -1
  134. package/dist/hooks/use-escape-key.js +15 -4
  135. package/dist/hooks/use-escape-key.js.map +1 -1
  136. package/dist/hooks/use-escape-key.test.js +135 -28
  137. package/dist/hooks/use-escape-key.test.js.map +1 -1
  138. package/dist/hooks/use-input-history.test.js +14 -8
  139. package/dist/hooks/use-input-history.test.js.map +1 -1
  140. package/dist/hooks/use-mode-handlers.d.ts +12 -1
  141. package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
  142. package/dist/hooks/use-mode-handlers.js +31 -1
  143. package/dist/hooks/use-mode-handlers.js.map +1 -1
  144. package/dist/hooks/use-model-selection.d.ts +6 -1
  145. package/dist/hooks/use-model-selection.d.ts.map +1 -1
  146. package/dist/hooks/use-model-selection.js +50 -8
  147. package/dist/hooks/use-model-selection.js.map +1 -1
  148. package/dist/hooks/use-model-selection.test.js +306 -50
  149. package/dist/hooks/use-model-selection.test.js.map +1 -1
  150. package/dist/hooks/use-path-completion.d.ts +4 -0
  151. package/dist/hooks/use-path-completion.d.ts.map +1 -1
  152. package/dist/hooks/use-path-completion.js +23 -17
  153. package/dist/hooks/use-path-completion.js.map +1 -1
  154. package/dist/hooks/use-path-completion.test.d.ts +2 -0
  155. package/dist/hooks/use-path-completion.test.d.ts.map +1 -0
  156. package/dist/hooks/use-path-completion.test.js +54 -0
  157. package/dist/hooks/use-path-completion.test.js.map +1 -0
  158. package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
  159. package/dist/hooks/use-runtime-settings.js +7 -2
  160. package/dist/hooks/use-runtime-settings.js.map +1 -1
  161. package/dist/hooks/use-runtime-settings.test.d.ts +2 -0
  162. package/dist/hooks/use-runtime-settings.test.d.ts.map +1 -0
  163. package/dist/hooks/use-runtime-settings.test.js +45 -0
  164. package/dist/hooks/use-runtime-settings.test.js.map +1 -0
  165. package/dist/hooks/use-selection.d.ts +6 -2
  166. package/dist/hooks/use-selection.d.ts.map +1 -1
  167. package/dist/hooks/use-selection.js +53 -16
  168. package/dist/hooks/use-selection.js.map +1 -1
  169. package/dist/hooks/use-selection.test.d.ts +2 -0
  170. package/dist/hooks/use-selection.test.d.ts.map +1 -0
  171. package/dist/hooks/use-selection.test.js +132 -0
  172. package/dist/hooks/use-selection.test.js.map +1 -0
  173. package/dist/hooks/use-settings-completion.d.ts +19 -0
  174. package/dist/hooks/use-settings-completion.d.ts.map +1 -1
  175. package/dist/hooks/use-settings-completion.js +106 -18
  176. package/dist/hooks/use-settings-completion.js.map +1 -1
  177. package/dist/hooks/use-settings-completion.scroll.test.js +68 -29
  178. package/dist/hooks/use-settings-completion.scroll.test.js.map +1 -1
  179. package/dist/hooks/use-settings-completion.test.js +36 -1
  180. package/dist/hooks/use-settings-completion.test.js.map +1 -1
  181. package/dist/hooks/use-settings-value-completion.d.ts +7 -1
  182. package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
  183. package/dist/hooks/use-settings-value-completion.js +18 -7
  184. package/dist/hooks/use-settings-value-completion.js.map +1 -1
  185. package/dist/hooks/use-slash-commands.d.ts +5 -0
  186. package/dist/hooks/use-slash-commands.d.ts.map +1 -1
  187. package/dist/hooks/use-slash-commands.js +22 -2
  188. package/dist/hooks/use-slash-commands.js.map +1 -1
  189. package/dist/hooks/use-trigger-detection.test.js +9 -6
  190. package/dist/hooks/use-trigger-detection.test.js.map +1 -1
  191. package/dist/hooks/use-undo-selection.d.ts +4 -0
  192. package/dist/hooks/use-undo-selection.d.ts.map +1 -1
  193. package/dist/hooks/use-undo-selection.js +18 -1
  194. package/dist/hooks/use-undo-selection.js.map +1 -1
  195. package/dist/inspect_zod.d.ts +2 -0
  196. package/dist/inspect_zod.d.ts.map +1 -0
  197. package/dist/inspect_zod.js +16 -0
  198. package/dist/inspect_zod.js.map +1 -0
  199. package/dist/lib/openai-agent-client.d.ts +10 -3
  200. package/dist/lib/openai-agent-client.d.ts.map +1 -1
  201. package/dist/lib/openai-agent-client.js +191 -37
  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/tool-invoke.d.ts +3 -2
  208. package/dist/lib/tool-invoke.d.ts.map +1 -1
  209. package/dist/lib/tool-invoke.js +213 -10
  210. package/dist/lib/tool-invoke.js.map +1 -1
  211. package/dist/lib/tool-invoke.test.js +103 -0
  212. package/dist/lib/tool-invoke.test.js.map +1 -1
  213. package/dist/prompts/codex.md +105 -97
  214. package/dist/prompts/fragments/gpt-5.3-codex.md +7 -0
  215. package/dist/prompts/fragments/gpt-5.4-small.md +6 -0
  216. package/dist/prompts/fragments/gpt-5.4.md +7 -0
  217. package/dist/prompts/fragments/gpt-5.5.md +7 -0
  218. package/dist/prompts/gpt-5-modern.md +96 -54
  219. package/dist/prompts/gpt-5.md +7 -4
  220. package/dist/prompts/plan-mode-info.md +14 -3
  221. package/dist/prompts/prompt-constructor.d.ts +19 -0
  222. package/dist/prompts/prompt-constructor.d.ts.map +1 -0
  223. package/dist/prompts/prompt-constructor.js +53 -0
  224. package/dist/prompts/prompt-constructor.js.map +1 -0
  225. package/dist/prompts/prompt-constructor.test.d.ts +2 -0
  226. package/dist/prompts/prompt-constructor.test.d.ts.map +1 -0
  227. package/dist/prompts/prompt-constructor.test.js +99 -0
  228. package/dist/prompts/prompt-constructor.test.js.map +1 -0
  229. package/dist/prompts/prompt-constructor.test.ts +112 -0
  230. package/dist/prompts/prompt-constructor.ts +91 -0
  231. package/dist/prompts/prompt-profiles.d.ts +17 -0
  232. package/dist/prompts/prompt-profiles.d.ts.map +1 -0
  233. package/dist/prompts/prompt-profiles.js +61 -0
  234. package/dist/prompts/prompt-profiles.js.map +1 -0
  235. package/dist/prompts/prompt-profiles.ts +77 -0
  236. package/dist/prompts/prompt-selector.d.ts.map +1 -1
  237. package/dist/prompts/prompt-selector.js +3 -21
  238. package/dist/prompts/prompt-selector.js.map +1 -1
  239. package/dist/prompts/prompt-selector.ts +3 -25
  240. package/dist/prompts/reasoning-efficiency.d.ts.map +1 -1
  241. package/dist/prompts/reasoning-efficiency.js +2 -0
  242. package/dist/prompts/reasoning-efficiency.js.map +1 -1
  243. package/dist/prompts/reasoning-efficiency.test.d.ts +2 -0
  244. package/dist/prompts/reasoning-efficiency.test.d.ts.map +1 -0
  245. package/dist/prompts/reasoning-efficiency.test.js +11 -0
  246. package/dist/prompts/reasoning-efficiency.test.js.map +1 -0
  247. package/dist/prompts/reasoning-efficiency.test.ts +12 -0
  248. package/dist/prompts/reasoning-efficiency.ts +2 -0
  249. package/dist/prompts/search-via-shell.test.js +19 -16
  250. package/dist/prompts/search-via-shell.test.js.map +1 -1
  251. package/dist/prompts/search-via-shell.test.ts +20 -16
  252. package/dist/prompts/subagent-delegation.test.js +12 -10
  253. package/dist/prompts/subagent-delegation.test.js.map +1 -1
  254. package/dist/prompts/subagent-delegation.test.ts +13 -10
  255. package/dist/prompts/worktree-hygiene.md +9 -0
  256. package/dist/providers/ai-sdk-logging-fetch.d.ts.map +1 -1
  257. package/dist/providers/ai-sdk-logging-fetch.js +78 -3
  258. package/dist/providers/ai-sdk-logging-fetch.js.map +1 -1
  259. package/dist/providers/ai-sdk-logging-fetch.test.js +143 -0
  260. package/dist/providers/ai-sdk-logging-fetch.test.js.map +1 -1
  261. package/dist/providers/ai-sdk-openai-compatible.provider.d.ts +0 -2
  262. package/dist/providers/ai-sdk-openai-compatible.provider.d.ts.map +1 -1
  263. package/dist/providers/ai-sdk-openai-compatible.provider.js +4 -8
  264. package/dist/providers/ai-sdk-openai-compatible.provider.js.map +1 -1
  265. package/dist/providers/ai-sdk-openai-compatible.provider.test.js +1 -119
  266. package/dist/providers/ai-sdk-openai-compatible.provider.test.js.map +1 -1
  267. package/dist/providers/codex-responses-model.d.ts +20 -0
  268. package/dist/providers/codex-responses-model.d.ts.map +1 -0
  269. package/dist/providers/codex-responses-model.js +170 -0
  270. package/dist/providers/codex-responses-model.js.map +1 -0
  271. package/dist/providers/codex-responses-model.test.d.ts +2 -0
  272. package/dist/providers/codex-responses-model.test.d.ts.map +1 -0
  273. package/dist/providers/codex-responses-model.test.js +240 -0
  274. package/dist/providers/codex-responses-model.test.js.map +1 -0
  275. package/dist/providers/codex.provider.d.ts +29 -0
  276. package/dist/providers/codex.provider.d.ts.map +1 -0
  277. package/dist/providers/codex.provider.js +486 -0
  278. package/dist/providers/codex.provider.js.map +1 -0
  279. package/dist/providers/codex.provider.test.d.ts +2 -0
  280. package/dist/providers/codex.provider.test.d.ts.map +1 -0
  281. package/dist/providers/codex.provider.test.js +634 -0
  282. package/dist/providers/codex.provider.test.js.map +1 -0
  283. package/dist/providers/custom-provider-adapter.test.js +56 -0
  284. package/dist/providers/custom-provider-adapter.test.js.map +1 -1
  285. package/dist/providers/fallback-responses-model.d.ts +20 -0
  286. package/dist/providers/fallback-responses-model.d.ts.map +1 -0
  287. package/dist/providers/fallback-responses-model.js +277 -0
  288. package/dist/providers/fallback-responses-model.js.map +1 -0
  289. package/dist/providers/fallback-responses-model.test.d.ts +2 -0
  290. package/dist/providers/fallback-responses-model.test.d.ts.map +1 -0
  291. package/dist/providers/fallback-responses-model.test.js +279 -0
  292. package/dist/providers/fallback-responses-model.test.js.map +1 -0
  293. package/dist/providers/fetch/compose.d.ts +7 -0
  294. package/dist/providers/fetch/compose.d.ts.map +1 -0
  295. package/dist/providers/fetch/compose.js +20 -0
  296. package/dist/providers/fetch/compose.js.map +1 -0
  297. package/dist/providers/fetch/composer.d.ts +13 -0
  298. package/dist/providers/fetch/composer.d.ts.map +1 -0
  299. package/dist/providers/fetch/composer.js +15 -0
  300. package/dist/providers/fetch/composer.js.map +1 -0
  301. package/dist/providers/fetch/composer.test.d.ts +2 -0
  302. package/dist/providers/fetch/composer.test.d.ts.map +1 -0
  303. package/dist/providers/fetch/composer.test.js +448 -0
  304. package/dist/providers/fetch/composer.test.js.map +1 -0
  305. package/dist/providers/fetch/logging-middleware.d.ts +16 -0
  306. package/dist/providers/fetch/logging-middleware.d.ts.map +1 -0
  307. package/dist/providers/fetch/logging-middleware.js +214 -0
  308. package/dist/providers/fetch/logging-middleware.js.map +1 -0
  309. package/dist/providers/index.d.ts +1 -0
  310. package/dist/providers/index.d.ts.map +1 -1
  311. package/dist/providers/index.js +1 -0
  312. package/dist/providers/index.js.map +1 -1
  313. package/dist/providers/llama-cpp.provider.d.ts +3 -0
  314. package/dist/providers/llama-cpp.provider.d.ts.map +1 -0
  315. package/dist/providers/llama-cpp.provider.js +34 -0
  316. package/dist/providers/llama-cpp.provider.js.map +1 -0
  317. package/dist/providers/openai-compatible-behavior-gate.test.js +1 -42
  318. package/dist/providers/openai-compatible-behavior-gate.test.js.map +1 -1
  319. package/dist/providers/openai-compatible-middleware.d.ts +7 -0
  320. package/dist/providers/openai-compatible-middleware.d.ts.map +1 -0
  321. package/dist/providers/openai-compatible-middleware.js +133 -0
  322. package/dist/providers/openai-compatible-middleware.js.map +1 -0
  323. package/dist/providers/openai-compatible-models.d.ts +6 -0
  324. package/dist/providers/openai-compatible-models.d.ts.map +1 -0
  325. package/dist/providers/openai-compatible-models.js +17 -0
  326. package/dist/providers/openai-compatible-models.js.map +1 -0
  327. package/dist/providers/openai-compatible-response-normalizer.d.ts +7 -0
  328. package/dist/providers/openai-compatible-response-normalizer.d.ts.map +1 -0
  329. package/dist/providers/openai-compatible-response-normalizer.js +58 -0
  330. package/dist/providers/openai-compatible-response-normalizer.js.map +1 -0
  331. package/dist/providers/openai-compatible.provider.d.ts +4 -1
  332. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  333. package/dist/providers/openai-compatible.provider.js +68 -324
  334. package/dist/providers/openai-compatible.provider.js.map +1 -1
  335. package/dist/providers/openai-compatible.provider.test.js +106 -1
  336. package/dist/providers/openai-compatible.provider.test.js.map +1 -1
  337. package/dist/providers/openai.provider.js +42 -10
  338. package/dist/providers/openai.provider.js.map +1 -1
  339. package/dist/providers/openai.provider.test.d.ts +2 -0
  340. package/dist/providers/openai.provider.test.d.ts.map +1 -0
  341. package/dist/providers/openai.provider.test.js +22 -0
  342. package/dist/providers/openai.provider.test.js.map +1 -0
  343. package/dist/providers/opencode.provider.d.ts +19 -0
  344. package/dist/providers/opencode.provider.d.ts.map +1 -0
  345. package/dist/providers/opencode.provider.js +45 -0
  346. package/dist/providers/opencode.provider.js.map +1 -0
  347. package/dist/providers/openrouter.provider.d.ts +2 -3
  348. package/dist/providers/openrouter.provider.d.ts.map +1 -1
  349. package/dist/providers/openrouter.provider.js +23 -24
  350. package/dist/providers/openrouter.provider.js.map +1 -1
  351. package/dist/providers/openrouter.provider.request-preprocessing.test.js +12 -9
  352. package/dist/providers/openrouter.provider.request-preprocessing.test.js.map +1 -1
  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 +6 -22
  357. package/dist/providers/registry.test.js.map +1 -1
  358. package/dist/scripts/eval-auto-approval/dataset.d.ts +2 -2
  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.js +38 -66
  370. package/dist/services/conversation-clear-save.test.js.map +1 -1
  371. package/dist/services/conversation-events.d.ts +44 -1
  372. package/dist/services/conversation-events.d.ts.map +1 -1
  373. package/dist/services/conversation-integration.test.js +3 -1
  374. package/dist/services/conversation-integration.test.js.map +1 -1
  375. package/dist/services/conversation-log-events.d.ts +107 -0
  376. package/dist/services/conversation-log-events.d.ts.map +1 -0
  377. package/dist/services/conversation-log-events.js +12 -0
  378. package/dist/services/conversation-log-events.js.map +1 -0
  379. package/dist/services/conversation-log-writer.d.ts +40 -0
  380. package/dist/services/conversation-log-writer.d.ts.map +1 -0
  381. package/dist/services/conversation-log-writer.js +264 -0
  382. package/dist/services/conversation-log-writer.js.map +1 -0
  383. package/dist/services/conversation-persistence-types.d.ts +14 -0
  384. package/dist/services/conversation-persistence-types.d.ts.map +1 -0
  385. package/dist/services/conversation-persistence-types.js +2 -0
  386. package/dist/services/conversation-persistence-types.js.map +1 -0
  387. package/dist/services/conversation-persistence.d.ts +38 -39
  388. package/dist/services/conversation-persistence.d.ts.map +1 -1
  389. package/dist/services/conversation-persistence.js +270 -82
  390. package/dist/services/conversation-persistence.js.map +1 -1
  391. package/dist/services/conversation-persistence.test.js +472 -637
  392. package/dist/services/conversation-persistence.test.js.map +1 -1
  393. package/dist/services/conversation-replay.d.ts +26 -0
  394. package/dist/services/conversation-replay.d.ts.map +1 -0
  395. package/dist/services/conversation-replay.js +252 -0
  396. package/dist/services/conversation-replay.js.map +1 -0
  397. package/dist/services/conversation-replay.test.d.ts +2 -0
  398. package/dist/services/conversation-replay.test.d.ts.map +1 -0
  399. package/dist/services/conversation-replay.test.js +153 -0
  400. package/dist/services/conversation-replay.test.js.map +1 -0
  401. package/dist/services/conversation-result-builder.d.ts.map +1 -1
  402. package/dist/services/conversation-result-builder.js +8 -2
  403. package/dist/services/conversation-result-builder.js.map +1 -1
  404. package/dist/services/conversation-result-builder.test.js +53 -0
  405. package/dist/services/conversation-result-builder.test.js.map +1 -1
  406. package/dist/services/conversation-retry-policy.d.ts +17 -0
  407. package/dist/services/conversation-retry-policy.d.ts.map +1 -1
  408. package/dist/services/conversation-retry-policy.js +35 -5
  409. package/dist/services/conversation-retry-policy.js.map +1 -1
  410. package/dist/services/conversation-retry-policy.test.js +56 -1
  411. package/dist/services/conversation-retry-policy.test.js.map +1 -1
  412. package/dist/services/conversation-service.d.ts +9 -0
  413. package/dist/services/conversation-service.d.ts.map +1 -1
  414. package/dist/services/conversation-service.js +18 -0
  415. package/dist/services/conversation-service.js.map +1 -1
  416. package/dist/services/conversation-session.d.ts +14 -0
  417. package/dist/services/conversation-session.d.ts.map +1 -1
  418. package/dist/services/conversation-session.js +426 -51
  419. package/dist/services/conversation-session.js.map +1 -1
  420. package/dist/services/input-surge-guard.d.ts +4 -0
  421. package/dist/services/input-surge-guard.d.ts.map +1 -1
  422. package/dist/services/input-surge-guard.js +2 -2
  423. package/dist/services/input-surge-guard.js.map +1 -1
  424. package/dist/services/input-surge-guard.test.js +15 -1
  425. package/dist/services/input-surge-guard.test.js.map +1 -1
  426. package/dist/services/large-uncached-input-guard.d.ts +35 -0
  427. package/dist/services/large-uncached-input-guard.d.ts.map +1 -0
  428. package/dist/services/large-uncached-input-guard.js +127 -0
  429. package/dist/services/large-uncached-input-guard.js.map +1 -0
  430. package/dist/services/large-uncached-input-guard.test.d.ts +2 -0
  431. package/dist/services/large-uncached-input-guard.test.d.ts.map +1 -0
  432. package/dist/services/large-uncached-input-guard.test.js +160 -0
  433. package/dist/services/large-uncached-input-guard.test.js.map +1 -0
  434. package/dist/services/model-service.d.ts +2 -0
  435. package/dist/services/model-service.d.ts.map +1 -1
  436. package/dist/services/model-service.js +7 -0
  437. package/dist/services/model-service.js.map +1 -1
  438. package/dist/services/provider-traffic.d.ts.map +1 -1
  439. package/dist/services/provider-traffic.js +82 -24
  440. package/dist/services/provider-traffic.js.map +1 -1
  441. package/dist/services/provider-traffic.test.js +98 -6
  442. package/dist/services/provider-traffic.test.js.map +1 -1
  443. package/dist/services/rtk-service.d.ts.map +1 -1
  444. package/dist/services/rtk-service.js +1 -2
  445. package/dist/services/rtk-service.js.map +1 -1
  446. package/dist/services/settings-schema.d.ts +36 -28
  447. package/dist/services/settings-schema.d.ts.map +1 -1
  448. package/dist/services/settings-schema.js +7 -2
  449. package/dist/services/settings-schema.js.map +1 -1
  450. package/dist/services/settings-sources.d.ts.map +1 -1
  451. package/dist/services/settings-sources.js +1 -0
  452. package/dist/services/settings-sources.js.map +1 -1
  453. package/dist/services/stream-event-processor.d.ts +4 -0
  454. package/dist/services/stream-event-processor.d.ts.map +1 -1
  455. package/dist/services/stream-event-processor.js +78 -39
  456. package/dist/services/stream-event-processor.js.map +1 -1
  457. package/dist/services/stream-event-processor.test.js +71 -0
  458. package/dist/services/stream-event-processor.test.js.map +1 -1
  459. package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
  460. package/dist/services/subagents/subagent-manager.js +88 -16
  461. package/dist/services/subagents/subagent-manager.js.map +1 -1
  462. package/dist/services/subagents/subagent-manager.test.js +354 -0
  463. package/dist/services/subagents/subagent-manager.test.js.map +1 -1
  464. package/dist/services/terminal-result-collector.d.ts.map +1 -1
  465. package/dist/services/terminal-result-collector.js +6 -1
  466. package/dist/services/terminal-result-collector.js.map +1 -1
  467. package/dist/services/tool-call-arguments.d.ts +20 -0
  468. package/dist/services/tool-call-arguments.d.ts.map +1 -0
  469. package/dist/services/tool-call-arguments.js +49 -0
  470. package/dist/services/tool-call-arguments.js.map +1 -0
  471. package/dist/services/tool-execution-ledger.d.ts +35 -0
  472. package/dist/services/tool-execution-ledger.d.ts.map +1 -0
  473. package/dist/services/tool-execution-ledger.js +204 -0
  474. package/dist/services/tool-execution-ledger.js.map +1 -0
  475. package/dist/services/tool-execution-ledger.test.d.ts +2 -0
  476. package/dist/services/tool-execution-ledger.test.d.ts.map +1 -0
  477. package/dist/services/tool-execution-ledger.test.js +96 -0
  478. package/dist/services/tool-execution-ledger.test.js.map +1 -0
  479. package/dist/tools/apply-patch.d.ts +8 -0
  480. package/dist/tools/apply-patch.d.ts.map +1 -1
  481. package/dist/tools/apply-patch.js +157 -2
  482. package/dist/tools/apply-patch.js.map +1 -1
  483. package/dist/tools/apply-patch.test.js +81 -0
  484. package/dist/tools/apply-patch.test.js.map +1 -1
  485. package/dist/tools/search-replace.d.ts.map +1 -1
  486. package/dist/tools/search-replace.js +92 -140
  487. package/dist/tools/search-replace.js.map +1 -1
  488. package/dist/utils/conversation-event-handler.d.ts.map +1 -1
  489. package/dist/utils/conversation-event-handler.js +33 -1
  490. package/dist/utils/conversation-event-handler.js.map +1 -1
  491. package/dist/utils/conversation-event-handler.test.js +103 -0
  492. package/dist/utils/conversation-event-handler.test.js.map +1 -1
  493. package/dist/utils/error-helpers.d.ts +1 -0
  494. package/dist/utils/error-helpers.d.ts.map +1 -1
  495. package/dist/utils/error-helpers.js +61 -0
  496. package/dist/utils/error-helpers.js.map +1 -1
  497. package/dist/utils/error-helpers.test.js +13 -1
  498. package/dist/utils/error-helpers.test.js.map +1 -1
  499. package/dist/utils/ink-render-options.d.ts +1 -1
  500. package/dist/utils/ink-render-options.d.ts.map +1 -1
  501. package/dist/utils/ink-render-options.js +1 -1
  502. package/dist/utils/ink-render-options.js.map +1 -1
  503. package/dist/utils/project-tree.d.ts +14 -0
  504. package/dist/utils/project-tree.d.ts.map +1 -0
  505. package/dist/utils/project-tree.js +111 -0
  506. package/dist/utils/project-tree.js.map +1 -0
  507. package/dist/utils/project-tree.test.d.ts +2 -0
  508. package/dist/utils/project-tree.test.d.ts.map +1 -0
  509. package/dist/utils/project-tree.test.js +149 -0
  510. package/dist/utils/project-tree.test.js.map +1 -0
  511. package/dist/utils/streaming-session-factory.d.ts +2 -0
  512. package/dist/utils/streaming-session-factory.d.ts.map +1 -1
  513. package/dist/utils/streaming-session-factory.js +4 -0
  514. package/dist/utils/streaming-session-factory.js.map +1 -1
  515. package/dist/utils/terminal-clear.d.ts +7 -0
  516. package/dist/utils/terminal-clear.d.ts.map +1 -0
  517. package/dist/utils/terminal-clear.js +5 -0
  518. package/dist/utils/terminal-clear.js.map +1 -0
  519. package/dist/utils/terminal-clear.test.d.ts +2 -0
  520. package/dist/utils/terminal-clear.test.d.ts.map +1 -0
  521. package/dist/utils/terminal-clear.test.js +13 -0
  522. package/dist/utils/terminal-clear.test.js.map +1 -0
  523. package/package.json +2 -2
  524. package/dist/agent.lite-mode.test.d.ts +0 -2
  525. package/dist/agent.lite-mode.test.d.ts.map +0 -1
  526. package/dist/agent.lite-mode.test.js +0 -39
  527. package/dist/agent.lite-mode.test.js.map +0 -1
  528. package/dist/components/ApprovalPrompt.test.d.ts +0 -2
  529. package/dist/components/ApprovalPrompt.test.d.ts.map +0 -1
  530. package/dist/components/ApprovalPrompt.test.js +0 -23
  531. package/dist/components/ApprovalPrompt.test.js.map +0 -1
  532. package/dist/components/LiveResponse.test.d.ts +0 -2
  533. package/dist/components/LiveResponse.test.d.ts.map +0 -1
  534. package/dist/components/LiveResponse.test.js +0 -13
  535. package/dist/components/LiveResponse.test.js.map +0 -1
  536. package/dist/components/ModelSelectionMenu.error-tabs.test.d.ts +0 -2
  537. package/dist/components/ModelSelectionMenu.error-tabs.test.d.ts.map +0 -1
  538. package/dist/components/ModelSelectionMenu.error-tabs.test.js +0 -18
  539. package/dist/components/ModelSelectionMenu.error-tabs.test.js.map +0 -1
  540. package/dist/components/TextInput.d.ts +0 -42
  541. package/dist/components/TextInput.d.ts.map +0 -1
  542. package/dist/components/TextInput.js +0 -398
  543. package/dist/components/TextInput.js.map +0 -1
  544. package/dist/components/TextInput.test.d.ts +0 -2
  545. package/dist/components/TextInput.test.d.ts.map +0 -1
  546. package/dist/components/TextInput.test.js +0 -75
  547. package/dist/components/TextInput.test.js.map +0 -1
  548. package/dist/debug_ask_mentor.d.ts +0 -2
  549. package/dist/debug_ask_mentor.d.ts.map +0 -1
  550. package/dist/debug_ask_mentor.js +0 -73
  551. package/dist/debug_ask_mentor.js.map +0 -1
  552. package/dist/hooks/use-conversation.max-turns-undo.test.d.ts +0 -2
  553. package/dist/hooks/use-conversation.max-turns-undo.test.d.ts.map +0 -1
  554. package/dist/hooks/use-conversation.max-turns-undo.test.js +0 -118
  555. package/dist/hooks/use-conversation.max-turns-undo.test.js.map +0 -1
  556. package/dist/lib/shell.d.ts +0 -7
  557. package/dist/lib/shell.d.ts.map +0 -1
  558. package/dist/lib/shell.js +0 -56
  559. package/dist/lib/shell.js.map +0 -1
  560. package/dist/modes/companion/command-index.d.ts +0 -26
  561. package/dist/modes/companion/command-index.d.ts.map +0 -1
  562. package/dist/modes/companion/command-index.js +0 -50
  563. package/dist/modes/companion/command-index.js.map +0 -1
  564. package/dist/modes/companion/command-index.test.d.ts +0 -2
  565. package/dist/modes/companion/command-index.test.d.ts.map +0 -1
  566. package/dist/modes/companion/command-index.test.js +0 -86
  567. package/dist/modes/companion/command-index.test.js.map +0 -1
  568. package/dist/modes/companion/companion-app.d.ts +0 -12
  569. package/dist/modes/companion/companion-app.d.ts.map +0 -1
  570. package/dist/modes/companion/companion-app.js +0 -297
  571. package/dist/modes/companion/companion-app.js.map +0 -1
  572. package/dist/modes/companion/companion-session.d.ts +0 -63
  573. package/dist/modes/companion/companion-session.d.ts.map +0 -1
  574. package/dist/modes/companion/companion-session.js +0 -146
  575. package/dist/modes/companion/companion-session.js.map +0 -1
  576. package/dist/modes/companion/companion-session.test.d.ts +0 -2
  577. package/dist/modes/companion/companion-session.test.d.ts.map +0 -1
  578. package/dist/modes/companion/companion-session.test.js +0 -28
  579. package/dist/modes/companion/companion-session.test.js.map +0 -1
  580. package/dist/modes/companion/components/status-bar.d.ts +0 -13
  581. package/dist/modes/companion/components/status-bar.d.ts.map +0 -1
  582. package/dist/modes/companion/components/status-bar.js +0 -26
  583. package/dist/modes/companion/components/status-bar.js.map +0 -1
  584. package/dist/modes/companion/context-buffer.d.ts +0 -65
  585. package/dist/modes/companion/context-buffer.d.ts.map +0 -1
  586. package/dist/modes/companion/context-buffer.js +0 -156
  587. package/dist/modes/companion/context-buffer.js.map +0 -1
  588. package/dist/modes/companion/context-buffer.test.d.ts +0 -2
  589. package/dist/modes/companion/context-buffer.test.d.ts.map +0 -1
  590. package/dist/modes/companion/context-buffer.test.js +0 -154
  591. package/dist/modes/companion/context-buffer.test.js.map +0 -1
  592. package/dist/modes/companion/event-detector.d.ts +0 -46
  593. package/dist/modes/companion/event-detector.d.ts.map +0 -1
  594. package/dist/modes/companion/event-detector.js +0 -169
  595. package/dist/modes/companion/event-detector.js.map +0 -1
  596. package/dist/modes/companion/event-detector.test.d.ts +0 -2
  597. package/dist/modes/companion/event-detector.test.d.ts.map +0 -1
  598. package/dist/modes/companion/event-detector.test.js +0 -121
  599. package/dist/modes/companion/event-detector.test.js.map +0 -1
  600. package/dist/modes/companion/index.d.ts +0 -33
  601. package/dist/modes/companion/index.d.ts.map +0 -1
  602. package/dist/modes/companion/index.js +0 -21
  603. package/dist/modes/companion/index.js.map +0 -1
  604. package/dist/modes/companion/input-key-mapper.d.ts +0 -3
  605. package/dist/modes/companion/input-key-mapper.d.ts.map +0 -1
  606. package/dist/modes/companion/input-key-mapper.js +0 -31
  607. package/dist/modes/companion/input-key-mapper.js.map +0 -1
  608. package/dist/modes/companion/input-key-mapper.test.d.ts +0 -2
  609. package/dist/modes/companion/input-key-mapper.test.d.ts.map +0 -1
  610. package/dist/modes/companion/input-key-mapper.test.js +0 -26
  611. package/dist/modes/companion/input-key-mapper.test.js.map +0 -1
  612. package/dist/modes/companion/input-parser.d.ts +0 -53
  613. package/dist/modes/companion/input-parser.d.ts.map +0 -1
  614. package/dist/modes/companion/input-parser.js +0 -114
  615. package/dist/modes/companion/input-parser.js.map +0 -1
  616. package/dist/modes/companion/input-parser.test.d.ts +0 -2
  617. package/dist/modes/companion/input-parser.test.d.ts.map +0 -1
  618. package/dist/modes/companion/input-parser.test.js +0 -123
  619. package/dist/modes/companion/input-parser.test.js.map +0 -1
  620. package/dist/modes/companion/mode-manager.d.ts +0 -41
  621. package/dist/modes/companion/mode-manager.d.ts.map +0 -1
  622. package/dist/modes/companion/mode-manager.js +0 -56
  623. package/dist/modes/companion/mode-manager.js.map +0 -1
  624. package/dist/modes/companion/mode-manager.test.d.ts +0 -2
  625. package/dist/modes/companion/mode-manager.test.d.ts.map +0 -1
  626. package/dist/modes/companion/mode-manager.test.js +0 -65
  627. package/dist/modes/companion/mode-manager.test.js.map +0 -1
  628. package/dist/modes/companion/output-classifier.d.ts +0 -15
  629. package/dist/modes/companion/output-classifier.d.ts.map +0 -1
  630. package/dist/modes/companion/output-classifier.js +0 -77
  631. package/dist/modes/companion/output-classifier.js.map +0 -1
  632. package/dist/modes/companion/output-classifier.test.d.ts +0 -2
  633. package/dist/modes/companion/output-classifier.test.d.ts.map +0 -1
  634. package/dist/modes/companion/output-classifier.test.js +0 -133
  635. package/dist/modes/companion/output-classifier.test.js.map +0 -1
  636. package/dist/modes/companion/pty-wrapper.d.ts +0 -46
  637. package/dist/modes/companion/pty-wrapper.d.ts.map +0 -1
  638. package/dist/modes/companion/pty-wrapper.js +0 -143
  639. package/dist/modes/companion/pty-wrapper.js.map +0 -1
  640. package/dist/modes/companion/safety-classifier.d.ts +0 -31
  641. package/dist/modes/companion/safety-classifier.d.ts.map +0 -1
  642. package/dist/modes/companion/safety-classifier.js +0 -140
  643. package/dist/modes/companion/safety-classifier.js.map +0 -1
  644. package/dist/modes/companion/safety-classifier.test.d.ts +0 -2
  645. package/dist/modes/companion/safety-classifier.test.d.ts.map +0 -1
  646. package/dist/modes/companion/safety-classifier.test.js +0 -151
  647. package/dist/modes/companion/safety-classifier.test.js.map +0 -1
  648. package/dist/modes/companion/summarizer.d.ts +0 -24
  649. package/dist/modes/companion/summarizer.d.ts.map +0 -1
  650. package/dist/modes/companion/summarizer.js +0 -132
  651. package/dist/modes/companion/summarizer.js.map +0 -1
  652. package/dist/modes/companion/terminal-history.d.ts +0 -27
  653. package/dist/modes/companion/terminal-history.d.ts.map +0 -1
  654. package/dist/modes/companion/terminal-history.js +0 -142
  655. package/dist/modes/companion/terminal-history.js.map +0 -1
  656. package/dist/prompts/simple-mentor.md +0 -215
  657. package/dist/providers/ai-sdk-openai.provider.d.ts +0 -14
  658. package/dist/providers/ai-sdk-openai.provider.d.ts.map +0 -1
  659. package/dist/providers/ai-sdk-openai.provider.js +0 -18
  660. package/dist/providers/ai-sdk-openai.provider.js.map +0 -1
  661. package/dist/providers/github-copilot/converters.d.ts +0 -45
  662. package/dist/providers/github-copilot/converters.d.ts.map +0 -1
  663. package/dist/providers/github-copilot/converters.js +0 -118
  664. package/dist/providers/github-copilot/converters.js.map +0 -1
  665. package/dist/providers/github-copilot/converters.test.d.ts +0 -2
  666. package/dist/providers/github-copilot/converters.test.d.ts.map +0 -1
  667. package/dist/providers/github-copilot/converters.test.js +0 -162
  668. package/dist/providers/github-copilot/converters.test.js.map +0 -1
  669. package/dist/providers/github-copilot/github-copilot.provider.d.ts +0 -2
  670. package/dist/providers/github-copilot/github-copilot.provider.d.ts.map +0 -1
  671. package/dist/providers/github-copilot/github-copilot.provider.js +0 -75
  672. package/dist/providers/github-copilot/github-copilot.provider.js.map +0 -1
  673. package/dist/providers/github-copilot/github-copilot.provider.test.d.ts +0 -2
  674. package/dist/providers/github-copilot/github-copilot.provider.test.d.ts.map +0 -1
  675. package/dist/providers/github-copilot/github-copilot.provider.test.js +0 -26
  676. package/dist/providers/github-copilot/github-copilot.provider.test.js.map +0 -1
  677. package/dist/providers/github-copilot/index.d.ts +0 -4
  678. package/dist/providers/github-copilot/index.d.ts.map +0 -1
  679. package/dist/providers/github-copilot/index.js +0 -4
  680. package/dist/providers/github-copilot/index.js.map +0 -1
  681. package/dist/providers/github-copilot/model.d.ts +0 -24
  682. package/dist/providers/github-copilot/model.d.ts.map +0 -1
  683. package/dist/providers/github-copilot/model.delta.test.d.ts +0 -2
  684. package/dist/providers/github-copilot/model.delta.test.d.ts.map +0 -1
  685. package/dist/providers/github-copilot/model.delta.test.js +0 -15
  686. package/dist/providers/github-copilot/model.delta.test.js.map +0 -1
  687. package/dist/providers/github-copilot/model.js +0 -498
  688. package/dist/providers/github-copilot/model.js.map +0 -1
  689. package/dist/providers/github-copilot/model.test.d.ts +0 -2
  690. package/dist/providers/github-copilot/model.test.d.ts.map +0 -1
  691. package/dist/providers/github-copilot/model.test.js +0 -63
  692. package/dist/providers/github-copilot/model.test.js.map +0 -1
  693. package/dist/providers/github-copilot/provider.d.ts +0 -15
  694. package/dist/providers/github-copilot/provider.d.ts.map +0 -1
  695. package/dist/providers/github-copilot/provider.js +0 -25
  696. package/dist/providers/github-copilot/provider.js.map +0 -1
  697. package/dist/providers/github-copilot/provider.test.d.ts +0 -2
  698. package/dist/providers/github-copilot/provider.test.d.ts.map +0 -1
  699. package/dist/providers/github-copilot/provider.test.js +0 -52
  700. package/dist/providers/github-copilot/provider.test.js.map +0 -1
  701. package/dist/providers/github-copilot/utils.d.ts +0 -20
  702. package/dist/providers/github-copilot/utils.d.ts.map +0 -1
  703. package/dist/providers/github-copilot/utils.js +0 -142
  704. package/dist/providers/github-copilot/utils.js.map +0 -1
  705. package/dist/providers/github-copilot/utils.test.d.ts +0 -2
  706. package/dist/providers/github-copilot/utils.test.d.ts.map +0 -1
  707. package/dist/providers/github-copilot/utils.test.js +0 -21
  708. package/dist/providers/github-copilot/utils.test.js.map +0 -1
  709. package/dist/providers/openai-compatible/api.d.ts +0 -17
  710. package/dist/providers/openai-compatible/api.d.ts.map +0 -1
  711. package/dist/providers/openai-compatible/api.js +0 -58
  712. package/dist/providers/openai-compatible/api.js.map +0 -1
  713. package/dist/providers/openai-compatible/model.d.ts +0 -17
  714. package/dist/providers/openai-compatible/model.d.ts.map +0 -1
  715. package/dist/providers/openai-compatible/model.js +0 -473
  716. package/dist/providers/openai-compatible/model.js.map +0 -1
  717. package/dist/providers/openai-compatible/provider.d.ts +0 -22
  718. package/dist/providers/openai-compatible/provider.d.ts.map +0 -1
  719. package/dist/providers/openai-compatible/provider.js +0 -43
  720. package/dist/providers/openai-compatible/provider.js.map +0 -1
  721. package/dist/providers/openai-compatible/reasoning-content.test.d.ts +0 -2
  722. package/dist/providers/openai-compatible/reasoning-content.test.d.ts.map +0 -1
  723. package/dist/providers/openai-compatible/reasoning-content.test.js +0 -258
  724. package/dist/providers/openai-compatible/reasoning-content.test.js.map +0 -1
  725. package/dist/providers/openai-compatible/utils.d.ts +0 -3
  726. package/dist/providers/openai-compatible/utils.d.ts.map +0 -1
  727. package/dist/providers/openai-compatible/utils.js +0 -11
  728. package/dist/providers/openai-compatible/utils.js.map +0 -1
  729. package/dist/providers/openrouter/api.d.ts +0 -39
  730. package/dist/providers/openrouter/api.d.ts.map +0 -1
  731. package/dist/providers/openrouter/api.js +0 -164
  732. package/dist/providers/openrouter/api.js.map +0 -1
  733. package/dist/providers/openrouter/converters.d.ts +0 -8
  734. package/dist/providers/openrouter/converters.d.ts.map +0 -1
  735. package/dist/providers/openrouter/converters.js +0 -391
  736. package/dist/providers/openrouter/converters.js.map +0 -1
  737. package/dist/providers/openrouter/converters.test.d.ts +0 -2
  738. package/dist/providers/openrouter/converters.test.d.ts.map +0 -1
  739. package/dist/providers/openrouter/converters.test.js +0 -179
  740. package/dist/providers/openrouter/converters.test.js.map +0 -1
  741. package/dist/providers/openrouter/index.d.ts +0 -4
  742. package/dist/providers/openrouter/index.d.ts.map +0 -1
  743. package/dist/providers/openrouter/index.js +0 -4
  744. package/dist/providers/openrouter/index.js.map +0 -1
  745. package/dist/providers/openrouter/model.d.ts +0 -14
  746. package/dist/providers/openrouter/model.d.ts.map +0 -1
  747. package/dist/providers/openrouter/model.js +0 -457
  748. package/dist/providers/openrouter/model.js.map +0 -1
  749. package/dist/providers/openrouter/provider.d.ts +0 -15
  750. package/dist/providers/openrouter/provider.d.ts.map +0 -1
  751. package/dist/providers/openrouter/provider.js +0 -21
  752. package/dist/providers/openrouter/provider.js.map +0 -1
  753. package/dist/providers/openrouter/reasoning-content.test.d.ts +0 -2
  754. package/dist/providers/openrouter/reasoning-content.test.d.ts.map +0 -1
  755. package/dist/providers/openrouter/reasoning-content.test.js +0 -122
  756. package/dist/providers/openrouter/reasoning-content.test.js.map +0 -1
  757. package/dist/providers/openrouter/utils.d.ts +0 -11
  758. package/dist/providers/openrouter/utils.d.ts.map +0 -1
  759. package/dist/providers/openrouter/utils.js +0 -29
  760. package/dist/providers/openrouter/utils.js.map +0 -1
  761. package/dist/providers/openrouter.api.retry.test.d.ts +0 -2
  762. package/dist/providers/openrouter.api.retry.test.d.ts.map +0 -1
  763. package/dist/providers/openrouter.api.retry.test.js +0 -148
  764. package/dist/providers/openrouter.api.retry.test.js.map +0 -1
  765. package/dist/providers/openrouter.test.d.ts +0 -2
  766. package/dist/providers/openrouter.test.d.ts.map +0 -1
  767. package/dist/providers/openrouter.test.js +0 -1449
  768. package/dist/providers/openrouter.test.js.map +0 -1
  769. package/dist/safety-checker.js +0 -57
  770. package/dist/services/check_mock.d.ts +0 -2
  771. package/dist/services/check_mock.d.ts.map +0 -1
  772. package/dist/services/check_mock.js +0 -22
  773. package/dist/services/check_mock.js.map +0 -1
  774. package/dist/services/conversation-service.test.d.ts +0 -2
  775. package/dist/services/conversation-service.test.d.ts.map +0 -1
  776. package/dist/services/conversation-service.test.js +0 -156
  777. package/dist/services/conversation-service.test.js.map +0 -1
  778. package/dist/services/subagents/reasoning-inheritance.test.d.ts +0 -2
  779. package/dist/services/subagents/reasoning-inheritance.test.d.ts.map +0 -1
  780. package/dist/services/subagents/reasoning-inheritance.test.js +0 -73
  781. package/dist/services/subagents/reasoning-inheritance.test.js.map +0 -1
  782. package/dist/tools/bash.d.ts +0 -10
  783. package/dist/tools/bash.d.ts.map +0 -1
  784. package/dist/tools/bash.js +0 -55
  785. package/dist/tools/bash.js.map +0 -1
  786. package/dist/utils/command-safety.d.ts +0 -21
  787. package/dist/utils/command-safety.d.ts.map +0 -1
  788. package/dist/utils/command-safety.devnull.test.d.ts +0 -2
  789. package/dist/utils/command-safety.devnull.test.d.ts.map +0 -1
  790. package/dist/utils/command-safety.devnull.test.js +0 -13
  791. package/dist/utils/command-safety.devnull.test.js.map +0 -1
  792. package/dist/utils/command-safety.js +0 -348
  793. package/dist/utils/command-safety.js.map +0 -1
  794. package/dist/utils/extract-command-messages.test.d.ts +0 -2
  795. package/dist/utils/extract-command-messages.test.d.ts.map +0 -1
  796. package/dist/utils/provider-traffic-extractor.repro.test.d.ts +0 -2
  797. package/dist/utils/provider-traffic-extractor.repro.test.d.ts.map +0 -1
  798. package/dist/utils/provider-traffic-extractor.repro.test.js.map +0 -1
@@ -3,18 +3,23 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import * as persistenceModule from './conversation-persistence.js';
6
+ import { createConversationLogWriter, LockConflictError } from './conversation-log-writer.js';
6
7
  let testDir = '';
7
- // Clean up all conversation files before and after each test
8
8
  function cleanupAll() {
9
9
  if (testDir && fs.existsSync(testDir)) {
10
10
  fs.rmSync(testDir, { recursive: true, force: true });
11
11
  }
12
12
  }
13
- function waitForDistinctTimestamp() {
14
- const start = Date.now();
15
- while (Date.now() - start < 10) {
16
- // busy wait for 10ms
17
- }
13
+ const stubLogger = {
14
+ error: () => { },
15
+ warn: () => { },
16
+ info: () => { },
17
+ debug: () => { },
18
+ trace: () => { },
19
+ getCorrelationId: () => undefined,
20
+ };
21
+ function emptySnapshot() {
22
+ return { history: [], previousResponseId: null, toolLedger: [] };
18
23
  }
19
24
  test.beforeEach(() => {
20
25
  testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'term2-conversations-test-'));
@@ -30,684 +35,514 @@ test.serial('generateId: returns a valid UUID', (t) => {
30
35
  t.regex(id, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
31
36
  });
32
37
  test.serial('generateId: returns unique IDs', (t) => {
33
- const id1 = persistenceModule.generateId();
34
- const id2 = persistenceModule.generateId();
35
- t.not(id1, id2);
38
+ t.not(persistenceModule.generateId(), persistenceModule.generateId());
36
39
  });
37
40
  test.serial('getResumeCommand: returns correct format', (t) => {
38
41
  const id = 'test-uuid-123';
39
- // Local (no SSH)
40
42
  t.is(persistenceModule.getResumeCommand(id), 'term2 --resume test-uuid-123');
41
- // SSH with host
42
43
  t.is(persistenceModule.getResumeCommand(id, 'user@host'), 'term2 --ssh user@host --resume test-uuid-123');
43
- // SSH with host and remote directory
44
- t.is(persistenceModule.getResumeCommand(id, 'user@host', '/path/to/project'), 'term2 --ssh user@host --remote-dir /path/to/project --resume test-uuid-123');
45
- // SSH with host, remote directory, and custom port
46
- t.is(persistenceModule.getResumeCommand(id, 'user@host', '/path/to/project', 2222), 'term2 --ssh user@host --remote-dir /path/to/project --ssh-port 2222 --resume test-uuid-123');
47
- // SSH with host and default port 22 (should be omitted)
48
- t.is(persistenceModule.getResumeCommand(id, 'user@host', '/path/to/project', 22), 'term2 --ssh user@host --remote-dir /path/to/project --resume test-uuid-123');
49
- });
50
- test.serial('saveConversation: creates file and last.json pointer', (t) => {
44
+ t.is(persistenceModule.getResumeCommand(id, 'user@host', '/path'), 'term2 --ssh user@host --remote-dir /path --resume test-uuid-123');
45
+ t.is(persistenceModule.getResumeCommand(id, 'user@host', '/path', 2222), 'term2 --ssh user@host --remote-dir /path --ssh-port 2222 --resume test-uuid-123');
46
+ });
47
+ test.serial('writer + loadConversation: round-trips a basic conversation', (t) => {
51
48
  const id = persistenceModule.generateId();
52
- const conversation = {
49
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
50
+ writer.init({
53
51
  id,
54
- createdAt: new Date().toISOString(),
55
- updatedAt: new Date().toISOString(),
52
+ createdAt: '2026-05-26T00:00:00.000Z',
53
+ projectPath: '/workspace/x',
56
54
  model: 'gpt-4o',
57
- previousResponseId: null,
58
- history: [{ role: 'user', content: 'hello' }],
59
- messages: [
60
- { id: 'msg-1', sender: 'user', text: 'hello' },
61
- { id: 'msg-2', sender: 'bot', text: 'hi there', status: 'finalized' },
62
- ],
63
- };
64
- const filePath = persistenceModule.saveConversation(conversation);
65
- t.true(fs.existsSync(filePath));
66
- // Verify last.json was created
67
- const lastPath = path.join(path.dirname(filePath), 'last.json');
68
- t.true(fs.existsSync(lastPath));
69
- const lastData = JSON.parse(fs.readFileSync(lastPath, 'utf-8'));
70
- t.is(lastData.id, id);
71
- // Cleanup
72
- fs.rmSync(path.dirname(filePath), { recursive: true, force: true });
73
- });
74
- test.serial('getConversationsDirForTest: uses isolated test override', (t) => {
75
- t.is(persistenceModule.getConversationsDirForTest(), testDir);
76
- });
77
- test.serial('saveConversation: normalizes streaming bot messages to finalized', (t) => {
55
+ provider: 'openai',
56
+ });
57
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hello' } });
58
+ writer.append({
59
+ type: 'assistant_final',
60
+ message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'hi there' },
61
+ finalText: 'hi there',
62
+ snapshot: {
63
+ history: [
64
+ { role: 'user', type: 'message', content: 'hello' },
65
+ { role: 'assistant', type: 'message', content: 'hi there' },
66
+ ],
67
+ previousResponseId: 'resp-1',
68
+ toolLedger: [],
69
+ model: 'gpt-4o',
70
+ provider: 'openai',
71
+ },
72
+ });
73
+ void writer.close();
74
+ const restored = persistenceModule.loadConversation(id);
75
+ t.truthy(restored);
76
+ t.is(restored.id, id);
77
+ t.is(restored.previousResponseId, 'resp-1');
78
+ t.is(restored.history.length, 2);
79
+ t.is(restored.messages.length, 2);
80
+ t.is(restored.messages[0].sender, 'user');
81
+ t.is(restored.messages[1].sender, 'bot');
82
+ });
83
+ test.serial('loadConversation: returns null for missing id', (t) => {
84
+ t.is(persistenceModule.loadConversation('nope'), null);
85
+ });
86
+ test.serial('replay: mid-turn crash with tool_started inserts recovery notice', (t) => {
78
87
  const id = persistenceModule.generateId();
79
- const conversation = {
80
- id,
81
- createdAt: new Date().toISOString(),
82
- updatedAt: new Date().toISOString(),
83
- previousResponseId: null,
84
- history: [],
85
- messages: [
86
- { id: 'msg-1', sender: 'user', text: 'hello' },
87
- { id: 'msg-2', sender: 'bot', text: 'thinking...', status: 'streaming' },
88
- ],
89
- };
90
- const filePath = persistenceModule.saveConversation(conversation);
91
- const saved = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
92
- t.is(saved.messages[1].status, 'finalized');
93
- // Cleanup
94
- fs.rmSync(path.dirname(filePath), { recursive: true, force: true });
95
- });
96
- test.serial('saveConversation: normalizes pending command messages to completed', (t) => {
88
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
89
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
90
+ // First turn completes cleanly
91
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'do it' } });
92
+ writer.append({
93
+ type: 'assistant_final',
94
+ message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'done' },
95
+ finalText: 'done',
96
+ snapshot: {
97
+ history: [{ role: 'user', type: 'message', content: 'do it' }],
98
+ previousResponseId: 'r1',
99
+ toolLedger: [],
100
+ },
101
+ });
102
+ // Second turn: user submits, tool starts, then crash
103
+ writer.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'more' } });
104
+ writer.append({ type: 'tool_started', toolCallId: 'call-1', toolName: 'shell', arguments: {} });
105
+ void writer.close();
106
+ const restored = persistenceModule.loadConversation(id);
107
+ t.truthy(restored);
108
+ t.true(restored.replayWarnings.some((w) => w.includes('interrupted')));
109
+ // Recovery system message present in history
110
+ const hasRecovery = restored.history.some((item) => item?.role === 'system' && typeof item.content === 'string' && item.content.includes('Recovered'));
111
+ t.true(hasRecovery);
112
+ // The interrupted system message is on UI messages
113
+ t.true(restored.messages.some((m) => m.sender === 'system' && String(m.text).includes('interrupted')));
114
+ });
115
+ test.serial('replay: user_message only with no assistant_final flags interruption', (t) => {
97
116
  const id = persistenceModule.generateId();
98
- const conversation = {
99
- id,
100
- createdAt: new Date().toISOString(),
101
- updatedAt: new Date().toISOString(),
102
- previousResponseId: null,
103
- history: [],
104
- messages: [{ id: 'msg-1', sender: 'command', status: 'pending', command: 'ls', toolName: 'shell' }],
105
- };
106
- const filePath = persistenceModule.saveConversation(conversation);
107
- const saved = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
108
- t.is(saved.messages[0].status, 'completed');
109
- t.is(saved.messages[0].success, false);
110
- // Cleanup
111
- fs.rmSync(path.dirname(filePath), { recursive: true, force: true });
112
- });
113
- test.serial('saveConversation: writes repaired history for duplicated tool replay artifacts', (t) => {
117
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
118
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
119
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi' } });
120
+ void writer.close();
121
+ const restored = persistenceModule.loadConversation(id);
122
+ t.truthy(restored);
123
+ t.true(restored.replayWarnings.length > 0);
124
+ t.true(restored.messages.some((m) => m.sender === 'system' && String(m.text).includes('interrupted')));
125
+ });
126
+ test.serial('replay: settings_changed updates restored model', (t) => {
114
127
  const id = persistenceModule.generateId();
115
- const conversation = {
116
- id,
117
- createdAt: new Date().toISOString(),
118
- updatedAt: new Date().toISOString(),
119
- previousResponseId: 'resp-123',
120
- history: [
121
- { role: 'user', type: 'message', content: 'Inspect' },
122
- { type: 'function_call', id: 'fc_1', callId: 'call-read', name: 'read_file', arguments: '{}' },
123
- { type: 'function_call_result', id: 'fcr_1', callId: 'call-read', output: 'contents' },
124
- { type: 'function_call', id: 'fc_2', callId: 'call-read', name: 'read_file', arguments: '{}' },
125
- { type: 'function_call_result', id: 'fcr_2', callId: 'call-read', output: 'contents' },
126
- ],
127
- messages: [{ id: 'msg-1', sender: 'user', text: 'Inspect' }],
128
- };
129
- const filePath = persistenceModule.saveConversation(conversation);
130
- const saved = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
131
- t.is(saved.previousResponseId, 'resp-123');
132
- t.is(saved.history.length, 3);
133
- t.deepEqual(saved.history.filter((item) => item.callId === 'call-read').map((item) => item.id), ['fc_1', 'fcr_1']);
134
- });
135
- test.serial('loadConversation: returns null for non-existent id', (t) => {
136
- const result = persistenceModule.loadConversation('non-existent-id');
137
- t.is(result, null);
138
- });
139
- test.serial('loadConversation: returns saved conversation', (t) => {
128
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
129
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z', model: 'gpt-4o' });
130
+ writer.append({ type: 'settings_changed', key: 'agent.model', value: 'gpt-5' });
131
+ writer.append({
132
+ type: 'assistant_final',
133
+ message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'ok' },
134
+ finalText: 'ok',
135
+ snapshot: { history: [], previousResponseId: null, toolLedger: [], model: 'gpt-5' },
136
+ });
137
+ void writer.close();
138
+ const restored = persistenceModule.loadConversation(id);
139
+ t.is(restored.model, 'gpt-5');
140
+ });
141
+ test.serial('replay: undo with snapshot replaces state', (t) => {
140
142
  const id = persistenceModule.generateId();
141
- const conversation = {
142
- id,
143
- createdAt: new Date().toISOString(),
144
- updatedAt: new Date().toISOString(),
145
- model: 'gpt-4o',
146
- previousResponseId: 'resp-123',
147
- history: [{ role: 'user', content: 'hello' }],
148
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
149
- };
150
- persistenceModule.saveConversation(conversation);
151
- const loaded = persistenceModule.loadConversation(id);
152
- t.truthy(loaded);
153
- t.is(loaded.id, id);
154
- t.is(loaded.model, 'gpt-4o');
155
- t.is(loaded.previousResponseId, 'resp-123');
156
- t.is(loaded.messages.length, 1);
157
- // Cleanup
158
- const filePath = path.join(persistenceModule.getConversationsDirForTest(), `${id}.json`);
159
- fs.rmSync(path.dirname(filePath), { recursive: true, force: true });
160
- });
161
- test.serial('loadConversation: returns repaired history with transient repair metadata', (t) => {
143
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
144
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
145
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'first' } });
146
+ writer.append({
147
+ type: 'assistant_final',
148
+ message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'A' },
149
+ finalText: 'A',
150
+ snapshot: {
151
+ history: [{ role: 'user', type: 'message', content: 'first' }],
152
+ previousResponseId: 'r1',
153
+ toolLedger: [],
154
+ },
155
+ });
156
+ writer.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'second' } });
157
+ writer.append({
158
+ type: 'assistant_final',
159
+ message: { id: 'b2', sender: 'bot', status: 'finalized', text: 'B' },
160
+ finalText: 'B',
161
+ snapshot: {
162
+ history: [{ role: 'user', type: 'message', content: 'first' }],
163
+ previousResponseId: 'r2',
164
+ toolLedger: [],
165
+ },
166
+ });
167
+ writer.append({ type: 'undo', removedUserTurns: 1, snapshot: emptySnapshot() });
168
+ void writer.close();
169
+ const restored = persistenceModule.loadConversation(id);
170
+ t.is(restored.history.length, 0);
171
+ t.is(restored.previousResponseId, null);
172
+ });
173
+ test.serial('replay: corrupt line is skipped', (t) => {
162
174
  const id = persistenceModule.generateId();
163
- const dir = persistenceModule.getConversationsDirForTest();
164
- fs.mkdirSync(dir, { recursive: true });
165
- fs.writeFileSync(path.join(dir, `${id}.json`), JSON.stringify({
166
- id,
167
- createdAt: new Date().toISOString(),
168
- updatedAt: new Date().toISOString(),
169
- previousResponseId: 'resp-123',
170
- history: [
171
- { role: 'user', type: 'message', content: 'Inspect' },
172
- { type: 'function_call', id: 'fc_1', callId: 'call-read', name: 'read_file', arguments: '{}' },
173
- { type: 'function_call_result', id: 'fcr_1', callId: 'call-read', output: 'contents' },
174
- { type: 'function_call', id: 'fc_2', callId: 'call-read', name: 'read_file', arguments: '{}' },
175
- { type: 'function_call_result', id: 'fcr_2', callId: 'call-read', output: 'contents' },
176
- ],
177
- messages: [{ id: 'msg-1', sender: 'user', text: 'Inspect' }],
178
- }, null, 2), 'utf-8');
179
- const loaded = persistenceModule.loadConversation(id);
180
- t.truthy(loaded);
181
- t.is(loaded.previousResponseId, 'resp-123');
182
- t.is(loaded.history.length, 3);
183
- t.true(loaded.historyRepair?.repaired);
184
- t.is(loaded.historyRepair?.removedItems, 2);
185
- });
186
- test.serial('loadConversation: preserves saved app mode settings', (t) => {
175
+ fs.mkdirSync(testDir, { recursive: true });
176
+ const filePath = path.join(testDir, `${id}.jsonl`);
177
+ const goodInit = JSON.stringify({
178
+ v: 1,
179
+ seq: 1,
180
+ ts: '2026-05-26T00:00:00.000Z',
181
+ event: { type: 'session_init', id, createdAt: '2026-05-26T00:00:00.000Z' },
182
+ });
183
+ const goodFinal = JSON.stringify({
184
+ v: 1,
185
+ seq: 3,
186
+ ts: '2026-05-26T00:00:01.000Z',
187
+ event: {
188
+ type: 'assistant_final',
189
+ message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'ok' },
190
+ finalText: 'ok',
191
+ snapshot: { history: [], previousResponseId: 'r1', toolLedger: [] },
192
+ },
193
+ });
194
+ fs.writeFileSync(filePath, `${goodInit}\n{not json\n${goodFinal}\n`, 'utf-8');
195
+ const restored = persistenceModule.loadConversation(id);
196
+ t.is(restored.previousResponseId, 'r1');
197
+ });
198
+ test.serial('lock: collision throws LockConflictError', (t) => {
187
199
  const id = persistenceModule.generateId();
188
- const conversation = {
189
- id,
190
- createdAt: new Date().toISOString(),
191
- updatedAt: new Date().toISOString(),
192
- appMode: {
193
- liteMode: false,
194
- mentorMode: true,
195
- planMode: true,
196
- orchestratorMode: false,
200
+ const w1 = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
201
+ w1.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
202
+ const w2 = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
203
+ t.throws(() => w2.init({ id, createdAt: '2026-05-26T00:00:00.000Z' }), { instanceOf: LockConflictError });
204
+ void w1.close();
205
+ });
206
+ test.serial('lock: released on writer close, second writer succeeds', async (t) => {
207
+ const id = persistenceModule.generateId();
208
+ const w1 = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
209
+ w1.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
210
+ await w1.close();
211
+ const w2 = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
212
+ t.notThrows(() => w2.init({ id, createdAt: '2026-05-26T00:00:00.000Z' }));
213
+ await w2.close();
214
+ });
215
+ test.serial('forkConversation: copies the source jsonl to a new id', (t) => {
216
+ const srcId = persistenceModule.generateId();
217
+ const dstId = persistenceModule.generateId();
218
+ const writer = createConversationLogWriter({ sessionId: srcId, dir: testDir, logger: stubLogger });
219
+ writer.init({ id: srcId, createdAt: '2026-05-26T00:00:00.000Z' });
220
+ writer.append({
221
+ type: 'assistant_final',
222
+ message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'A' },
223
+ finalText: 'A',
224
+ snapshot: {
225
+ history: [{ role: 'user', type: 'message', content: 'hi' }],
226
+ previousResponseId: 'r1',
227
+ toolLedger: [],
197
228
  },
198
- previousResponseId: null,
199
- history: [],
200
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
201
- };
202
- persistenceModule.saveConversation(conversation);
203
- const loaded = persistenceModule.loadConversation(id);
204
- t.deepEqual(loaded?.appMode, {
205
- liteMode: false,
206
- mentorMode: true,
207
- planMode: true,
208
- orchestratorMode: false,
209
229
  });
230
+ void writer.close();
231
+ t.true(persistenceModule.forkConversation(srcId, dstId));
232
+ const restored = persistenceModule.loadConversation(dstId);
233
+ t.is(restored.previousResponseId, 'r1');
210
234
  });
211
- test.serial('loadConversation: returns null when saved project path differs from expected path', (t) => {
235
+ test.serial('listConversations: lists sessions sorted by mtime desc', (t) => {
236
+ const id1 = persistenceModule.generateId();
237
+ const w1 = createConversationLogWriter({ sessionId: id1, dir: testDir, logger: stubLogger });
238
+ w1.init({ id: id1, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/p1' });
239
+ void w1.close();
240
+ // Sleep to ensure mtime differs
241
+ const target = Date.now() + 20;
242
+ while (Date.now() < target) {
243
+ /* spin */
244
+ }
245
+ const id2 = persistenceModule.generateId();
246
+ const w2 = createConversationLogWriter({ sessionId: id2, dir: testDir, logger: stubLogger });
247
+ w2.init({ id: id2, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/p2' });
248
+ void w2.close();
249
+ const list = persistenceModule.listConversations();
250
+ t.is(list.length, 2);
251
+ t.is(list[0].id, id2);
252
+ t.is(list[0].projectPath, '/p2');
253
+ });
254
+ test.serial('loadConversation: returns null when expected project path differs', (t) => {
212
255
  const id = persistenceModule.generateId();
213
- const conversation = {
214
- id,
215
- createdAt: new Date().toISOString(),
216
- updatedAt: new Date().toISOString(),
217
- projectPath: '/workspace/alpha',
218
- previousResponseId: null,
219
- history: [],
220
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
221
- };
222
- persistenceModule.saveConversation(conversation);
223
- const loaded = persistenceModule.loadConversation(id, '/workspace/beta');
224
- t.is(loaded, null);
225
- });
226
- test.serial('loadConversationForProject: reports project mismatch for existing conversation in another path', (t) => {
256
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
257
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/workspace/alpha' });
258
+ void writer.close();
259
+ t.is(persistenceModule.loadConversation(id, '/workspace/beta'), null);
260
+ });
261
+ test.serial('loadConversationForProject: reports project mismatch', (t) => {
227
262
  const id = persistenceModule.generateId();
228
- const conversation = {
229
- id,
230
- createdAt: new Date().toISOString(),
231
- updatedAt: new Date().toISOString(),
232
- projectPath: '/workspace/alpha',
233
- previousResponseId: null,
234
- history: [],
235
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
236
- };
237
- persistenceModule.saveConversation(conversation);
263
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
264
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/workspace/alpha' });
265
+ void writer.close();
238
266
  const result = persistenceModule.loadConversationForProject(id, '/workspace/beta');
239
267
  t.is(result.status, 'project_mismatch');
240
- if (result.status !== 'project_mismatch') {
241
- t.fail('expected project_mismatch');
242
- return;
243
- }
244
- t.is(result.conversation.id, id);
245
268
  });
246
- test.serial('loadConversationForProject: reports not_found for missing conversation', (t) => {
247
- const result = persistenceModule.loadConversationForProject('missing-id', '/workspace/beta');
248
- t.is(result.status, 'not_found');
269
+ test.serial('loadConversationForProject: not_found for missing', (t) => {
270
+ t.is(persistenceModule.loadConversationForProject('missing', '/x').status, 'not_found');
249
271
  });
250
- test.serial('loadConversation: returns saved conversation when expected project path matches', (t) => {
251
- const id = persistenceModule.generateId();
252
- const conversation = {
253
- id,
254
- createdAt: new Date().toISOString(),
255
- updatedAt: new Date().toISOString(),
256
- projectPath: '/workspace/alpha',
257
- previousResponseId: null,
258
- history: [],
259
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
260
- };
261
- persistenceModule.saveConversation(conversation);
262
- const loaded = persistenceModule.loadConversation(id, '/workspace/alpha');
263
- t.truthy(loaded);
264
- t.is(loaded.id, id);
265
- });
266
- test.serial('loadConversation: unscoped loads keep backward-compatible behavior', (t) => {
267
- const id = persistenceModule.generateId();
268
- const conversation = {
269
- id,
270
- createdAt: new Date().toISOString(),
271
- updatedAt: new Date().toISOString(),
272
- projectPath: '/workspace/alpha',
273
- previousResponseId: null,
274
- history: [],
275
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
276
- };
277
- persistenceModule.saveConversation(conversation);
278
- const loaded = persistenceModule.loadConversation(id);
279
- t.truthy(loaded);
280
- t.is(loaded.id, id);
281
- });
282
- test.serial('loadLastConversation: returns null when no last.json exists', (t) => {
283
- const result = persistenceModule.loadLastConversation();
284
- t.is(result, null);
285
- });
286
- test.serial('loadLastConversation: returns the last saved conversation', (t) => {
272
+ test.serial('loadLastConversation: returns the last written conversation', (t) => {
287
273
  const id1 = persistenceModule.generateId();
274
+ const w1 = createConversationLogWriter({ sessionId: id1, dir: testDir, logger: stubLogger });
275
+ w1.init({ id: id1, createdAt: '2026-05-26T00:00:00.000Z' });
276
+ w1.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi' } });
277
+ void w1.close();
278
+ const target = Date.now() + 20;
279
+ while (Date.now() < target) {
280
+ /* spin */
281
+ }
288
282
  const id2 = persistenceModule.generateId();
289
- const conv1 = {
290
- id: id1,
291
- createdAt: new Date().toISOString(),
292
- updatedAt: new Date().toISOString(),
293
- previousResponseId: null,
294
- history: [],
295
- messages: [{ id: 'msg-1', sender: 'user', text: 'first' }],
296
- };
297
- const conv2 = {
298
- id: id2,
299
- createdAt: new Date().toISOString(),
300
- updatedAt: new Date().toISOString(),
301
- previousResponseId: null,
302
- history: [],
303
- messages: [{ id: 'msg-2', sender: 'user', text: 'second' }],
304
- };
305
- persistenceModule.saveConversation(conv1);
306
- persistenceModule.saveConversation(conv2);
283
+ const w2 = createConversationLogWriter({ sessionId: id2, dir: testDir, logger: stubLogger });
284
+ w2.init({ id: id2, createdAt: '2026-05-26T00:00:00.000Z' });
285
+ w2.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'hi2' } });
286
+ void w2.close();
307
287
  const last = persistenceModule.loadLastConversation();
308
288
  t.truthy(last);
309
289
  t.is(last.id, id2);
310
- // Cleanup
311
- fs.rmSync(persistenceModule.getConversationsDirForTest(), { recursive: true, force: true });
312
290
  });
313
- test.serial('loadLastConversation: surfaces repaired conversation metadata', (t) => {
291
+ test.serial('loadLastConversation: does not save last.json for empty conversation', (t) => {
314
292
  const id = persistenceModule.generateId();
315
- const dir = persistenceModule.getConversationsDirForTest();
316
- fs.mkdirSync(dir, { recursive: true });
317
- fs.writeFileSync(path.join(dir, `${id}.json`), JSON.stringify({
318
- id,
319
- createdAt: new Date().toISOString(),
320
- updatedAt: new Date().toISOString(),
321
- previousResponseId: null,
322
- history: [
323
- { role: 'user', type: 'message', content: 'Inspect' },
324
- { type: 'function_call', id: 'fc_1', callId: 'call-read', name: 'read_file', arguments: '{}' },
325
- { type: 'function_call_result', id: 'fcr_1', callId: 'call-read', output: 'contents' },
326
- { type: 'function_call', id: 'fc_2', callId: 'call-read', name: 'read_file', arguments: '{}' },
327
- { type: 'function_call_result', id: 'fcr_2', callId: 'call-read', output: 'contents' },
328
- ],
329
- messages: [{ id: 'msg-1', sender: 'user', text: 'Inspect' }],
330
- }, null, 2), 'utf-8');
331
- fs.writeFileSync(path.join(dir, 'last.json'), JSON.stringify({ id }), 'utf-8');
293
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
294
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
295
+ void writer.close();
332
296
  const last = persistenceModule.loadLastConversation();
333
- t.truthy(last);
334
- t.true(last.historyRepair?.repaired);
335
- t.is(last.history.length, 3);
336
- });
337
- test.serial('loadLastConversation: returns most recent conversation for expected project path', (t) => {
338
- const projectAOlder = persistenceModule.generateId();
339
- const projectANewer = persistenceModule.generateId();
340
- const projectBNewer = persistenceModule.generateId();
341
- persistenceModule.saveConversation({
342
- id: projectAOlder,
343
- createdAt: '2024-01-01T00:00:00.000Z',
344
- updatedAt: '2024-01-01T00:00:00.000Z',
345
- projectPath: '/workspace/alpha',
346
- previousResponseId: null,
347
- history: [],
348
- messages: [{ id: 'msg-1', sender: 'user', text: 'alpha older' }],
349
- });
350
- waitForDistinctTimestamp();
351
- persistenceModule.saveConversation({
352
- id: projectANewer,
353
- createdAt: '2024-01-02T00:00:00.000Z',
354
- updatedAt: '2024-01-02T00:00:00.000Z',
355
- projectPath: '/workspace/alpha',
356
- previousResponseId: null,
357
- history: [],
358
- messages: [{ id: 'msg-2', sender: 'user', text: 'alpha newer' }],
359
- });
360
- waitForDistinctTimestamp();
361
- persistenceModule.saveConversation({
362
- id: projectBNewer,
363
- createdAt: '2024-01-03T00:00:00.000Z',
364
- updatedAt: '2024-01-03T00:00:00.000Z',
365
- projectPath: '/workspace/beta',
366
- previousResponseId: null,
367
- history: [],
368
- messages: [{ id: 'msg-3', sender: 'user', text: 'beta newer' }],
369
- });
370
- const last = persistenceModule.loadLastConversation('/workspace/alpha');
371
- t.truthy(last);
372
- t.is(last.id, projectANewer);
373
- });
374
- test.serial('loadLastConversation: returns null when no conversation matches expected project path', (t) => {
375
- persistenceModule.saveConversation({
376
- id: persistenceModule.generateId(),
377
- createdAt: new Date().toISOString(),
378
- updatedAt: new Date().toISOString(),
379
- projectPath: '/workspace/alpha',
380
- previousResponseId: null,
381
- history: [],
382
- messages: [{ id: 'msg-1', sender: 'user', text: 'alpha' }],
383
- });
384
- const last = persistenceModule.loadLastConversation('/workspace/beta');
385
297
  t.is(last, null);
298
+ t.false(fs.existsSync(path.join(testDir, 'last.json')));
386
299
  });
387
- test.serial('deleteConversation: removes the conversation file', (t) => {
388
- const id = persistenceModule.generateId();
389
- const conversation = {
390
- id,
391
- createdAt: new Date().toISOString(),
392
- updatedAt: new Date().toISOString(),
393
- previousResponseId: null,
394
- history: [],
395
- messages: [],
396
- };
397
- persistenceModule.saveConversation(conversation);
398
- const result = persistenceModule.deleteConversation(id);
399
- t.true(result);
400
- t.is(persistenceModule.loadConversation(id), null);
401
- // Cleanup
402
- fs.rmSync(persistenceModule.getConversationsDirForTest(), { recursive: true, force: true });
403
- });
404
- test.serial('deleteConversation: returns false for non-existent id', (t) => {
405
- const result = persistenceModule.deleteConversation('non-existent');
406
- t.false(result);
407
- });
408
- test.serial('listConversations: returns empty array when no conversations exist', (t) => {
409
- // Ensure dir exists but is empty
410
- const dir = persistenceModule.getConversationsDirForTest();
411
- if (fs.existsSync(dir)) {
412
- fs.rmSync(dir, { recursive: true, force: true });
413
- }
414
- const list = persistenceModule.listConversations();
415
- t.deepEqual(list, []);
300
+ test.serial('hasConversationContent: returns false for missing conversation', (t) => {
301
+ t.false(persistenceModule.hasConversationContent('non-existent-id'));
416
302
  });
417
- test.serial('loadConversation: old save without orchestratorMode — resolves to false, no error', (t) => {
303
+ test.serial('hasConversationContent: returns false for empty conversation', (t) => {
418
304
  const id = persistenceModule.generateId();
419
- // Write a raw JSON file that looks like an old save (missing orchestratorMode)
420
- const dir = persistenceModule.getConversationsDirForTest();
421
- if (!fs.existsSync(dir)) {
422
- fs.mkdirSync(dir, { recursive: true });
423
- }
424
- const rawData = {
425
- id,
426
- createdAt: new Date().toISOString(),
427
- updatedAt: new Date().toISOString(),
428
- appMode: {
429
- mentorMode: false,
430
- liteMode: false,
431
- planMode: false,
432
- // orchestratorMode is intentionally absent (old format)
433
- },
434
- previousResponseId: null,
435
- history: [],
436
- messages: [],
437
- };
438
- fs.writeFileSync(path.join(dir, `${id}.json`), JSON.stringify(rawData, null, 2), 'utf-8');
439
- // loadConversation should not throw — it returns null on error
440
- const loaded = persistenceModule.loadConversation(id);
441
- t.truthy(loaded);
442
- // orchestratorMode should be absent or false-y, not throw
443
- const orchestratorMode = loaded?.appMode?.orchestratorMode;
444
- t.falsy(orchestratorMode, 'orchestratorMode missing from old save should not be truthy');
445
- });
446
- test.serial('loadConversation: save with orchestratorMode: true is correctly restored', (t) => {
447
- const id = persistenceModule.generateId();
448
- const conversation = {
449
- id,
450
- createdAt: new Date().toISOString(),
451
- updatedAt: new Date().toISOString(),
452
- appMode: {
453
- mentorMode: false,
454
- liteMode: false,
455
- planMode: false,
456
- orchestratorMode: true,
457
- },
458
- previousResponseId: null,
459
- history: [],
460
- messages: [],
461
- };
462
- persistenceModule.saveConversation(conversation);
463
- const loaded = persistenceModule.loadConversation(id);
464
- t.truthy(loaded);
465
- t.is(loaded?.appMode?.orchestratorMode, true);
466
- });
467
- test.serial('listConversations: returns conversations sorted by updatedAt descending', (t) => {
468
- const id1 = persistenceModule.generateId();
469
- const id2 = persistenceModule.generateId();
470
- const conv1 = {
471
- id: id1,
472
- createdAt: '2024-01-01T00:00:00.000Z',
473
- updatedAt: '2024-01-01T00:00:00.000Z',
474
- previousResponseId: null,
475
- history: [],
476
- messages: [],
477
- };
478
- const conv2 = {
479
- id: id2,
480
- createdAt: '2024-01-02T00:00:00.000Z',
481
- updatedAt: '2024-01-02T00:00:00.000Z',
482
- previousResponseId: null,
483
- history: [],
484
- messages: [],
485
- };
486
- // Save conv1 first
487
- persistenceModule.saveConversation(conv1);
488
- // Wait a moment so updatedAt differs
489
- waitForDistinctTimestamp();
490
- // Save conv2 second (should have later updatedAt)
491
- persistenceModule.saveConversation(conv2);
492
- const list = persistenceModule.listConversations();
493
- t.is(list.length, 2);
494
- t.is(list[0].id, id2); // Most recently saved first
495
- t.is(list[1].id, id1);
305
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
306
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
307
+ void writer.close();
308
+ t.false(persistenceModule.hasConversationContent(id));
496
309
  });
497
- test.serial('conversationMatchesProject: loadConversation with matching sshHost returns conversation', (t) => {
498
- const id = persistenceModule.generateId();
499
- const conversation = {
500
- id,
501
- createdAt: new Date().toISOString(),
502
- updatedAt: new Date().toISOString(),
503
- sshHost: 'myhost.example.com',
504
- previousResponseId: null,
505
- history: [],
506
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
507
- };
508
- persistenceModule.saveConversation(conversation);
509
- const loaded = persistenceModule.loadConversation(id, undefined, 'myhost.example.com');
510
- t.truthy(loaded);
511
- t.is(loaded.id, id);
512
- t.is(loaded.sshHost, 'myhost.example.com');
513
- });
514
- test.serial('conversationMatchesProject: loadConversation with mismatched sshHost returns null', (t) => {
515
- const id = persistenceModule.generateId();
516
- const conversation = {
517
- id,
518
- createdAt: new Date().toISOString(),
519
- updatedAt: new Date().toISOString(),
520
- sshHost: 'myhost.example.com',
521
- previousResponseId: null,
522
- history: [],
523
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
524
- };
525
- persistenceModule.saveConversation(conversation);
526
- const loaded = persistenceModule.loadConversation(id, undefined, 'otherhost.example.com');
527
- t.is(loaded, null);
528
- });
529
- test.serial('conversationMatchesProject: loading remote conversation from local session (no expectedSshHost) returns null', (t) => {
530
- const id = persistenceModule.generateId();
531
- const conversation = {
532
- id,
533
- createdAt: new Date().toISOString(),
534
- updatedAt: new Date().toISOString(),
535
- sshHost: 'remote.example.com',
536
- projectPath: '/workspace/project',
537
- previousResponseId: null,
538
- history: [],
539
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
540
- };
541
- persistenceModule.saveConversation(conversation);
542
- // Local session (expectedProjectPath given, but no expectedSshHost)
543
- // should reject a remote conversation that has sshHost
544
- const loaded = persistenceModule.loadConversation(id, '/workspace/project', undefined);
545
- t.is(loaded, null);
546
- });
547
- test.serial('conversationMatchesProject: loading local conversation from remote session (no conversation.sshHost) returns null', (t) => {
310
+ test.serial('hasConversationContent: returns true for user_message', (t) => {
548
311
  const id = persistenceModule.generateId();
549
- const conversation = {
550
- id,
551
- createdAt: new Date().toISOString(),
552
- updatedAt: new Date().toISOString(),
553
- // no sshHost — local conversation
554
- projectPath: '/workspace/project',
555
- previousResponseId: null,
556
- history: [],
557
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
558
- };
559
- persistenceModule.saveConversation(conversation);
560
- // Remote session (expectedSshHost given) should reject a local conversation
561
- const loaded = persistenceModule.loadConversation(id, '/workspace/project', 'remote.example.com');
562
- t.is(loaded, null);
563
- });
564
- test.serial('loadConversationForProject: returns project_mismatch for sshHost mismatch', (t) => {
565
- const id = persistenceModule.generateId();
566
- const conversation = {
567
- id,
568
- createdAt: new Date().toISOString(),
569
- updatedAt: new Date().toISOString(),
570
- sshHost: 'myhost.example.com',
571
- projectPath: '/workspace/alpha',
572
- previousResponseId: null,
573
- history: [],
574
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
575
- };
576
- persistenceModule.saveConversation(conversation);
577
- const result = persistenceModule.loadConversationForProject(id, '/workspace/alpha', 'otherhost.example.com');
578
- t.is(result.status, 'project_mismatch');
579
- if (result.status === 'project_mismatch') {
580
- t.is(result.conversation.id, id);
581
- t.is(result.conversation.sshHost, 'myhost.example.com');
582
- }
312
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
313
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
314
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi' } });
315
+ void writer.close();
316
+ t.true(persistenceModule.hasConversationContent(id));
583
317
  });
584
- test.serial('loadConversationForProject: returns project_mismatch for project path mismatch with sshHost', (t) => {
318
+ test.serial('hasConversationContent: returns true for assistant_final', (t) => {
585
319
  const id = persistenceModule.generateId();
586
- const conversation = {
587
- id,
588
- createdAt: new Date().toISOString(),
589
- updatedAt: new Date().toISOString(),
590
- sshHost: 'myhost.example.com',
591
- projectPath: '/workspace/alpha',
592
- previousResponseId: null,
593
- history: [],
594
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
595
- };
596
- persistenceModule.saveConversation(conversation);
597
- const result = persistenceModule.loadConversationForProject(id, '/workspace/beta', 'myhost.example.com');
598
- t.is(result.status, 'project_mismatch');
599
- if (result.status === 'project_mismatch') {
600
- t.is(result.conversation.id, id);
601
- }
320
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
321
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
322
+ writer.append({
323
+ type: 'assistant_final',
324
+ message: { id: 'a1', sender: 'bot', status: 'finalized', text: 'hello' },
325
+ finalText: 'hello',
326
+ snapshot: emptySnapshot(),
327
+ });
328
+ void writer.close();
329
+ t.true(persistenceModule.hasConversationContent(id));
602
330
  });
603
- test.serial('loadConversationForProject: returns loaded when both sshHost and projectPath match', (t) => {
331
+ test.serial('hasConversationContent: skips corrupt lines and finds content', (t) => {
604
332
  const id = persistenceModule.generateId();
605
- const conversation = {
606
- id,
607
- createdAt: new Date().toISOString(),
608
- updatedAt: new Date().toISOString(),
609
- sshHost: 'myhost.example.com',
610
- projectPath: '/workspace/alpha',
611
- previousResponseId: null,
612
- history: [],
613
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
614
- };
615
- persistenceModule.saveConversation(conversation);
616
- const result = persistenceModule.loadConversationForProject(id, '/workspace/alpha', 'myhost.example.com');
617
- t.is(result.status, 'loaded');
618
- if (result.status === 'loaded') {
619
- t.is(result.conversation.id, id);
620
- t.is(result.conversation.sshHost, 'myhost.example.com');
621
- }
622
- });
623
- test.serial('loadConversationForProject: SSH host matching is case-insensitive', (t) => {
333
+ const filePath = path.join(testDir, `${id}.jsonl`);
334
+ fs.writeFileSync(filePath, '{"v":1,"seq":1,"ts":"2026-05-26T00:00:00.000Z","event":{"type":"session_init","id":"' +
335
+ id +
336
+ '","createdAt":"2026-05-26T00:00:00.000Z"}}\n' +
337
+ 'this is not json\n' +
338
+ '{"v":1,"seq":2,"ts":"2026-05-26T00:00:01.000Z","event":{"type":"user_message","message":{"id":"u1","sender":"user","text":"hi"}}}\n', 'utf-8');
339
+ t.true(persistenceModule.hasConversationContent(id));
340
+ });
341
+ test.serial('deleteConversation: removes the jsonl and clears last.json', (t) => {
624
342
  const id = persistenceModule.generateId();
625
- const conversation = {
626
- id,
627
- createdAt: new Date().toISOString(),
628
- updatedAt: new Date().toISOString(),
629
- sshHost: 'MyHost.EXAMPLE.COM',
630
- projectPath: '/workspace/alpha',
631
- previousResponseId: null,
632
- history: [],
633
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
634
- };
635
- persistenceModule.saveConversation(conversation);
636
- const result = persistenceModule.loadConversationForProject(id, '/workspace/alpha', 'myhost.example.com');
637
- t.is(result.status, 'loaded');
638
- if (result.status === 'loaded') {
639
- t.is(result.conversation.id, id);
640
- }
641
- });
642
- test.serial('loadLastConversation: filters by both expectedProjectPath and expectedSshHost', (t) => {
643
- const matchingId = persistenceModule.generateId();
644
- const wrongSshId = persistenceModule.generateId();
645
- const wrongProjectId = persistenceModule.generateId();
646
- persistenceModule.saveConversation({
647
- id: matchingId,
648
- createdAt: '2024-01-03T00:00:00.000Z',
649
- updatedAt: '2024-01-03T00:00:00.000Z',
650
- projectPath: '/workspace/alpha',
651
- sshHost: 'myhost.example.com',
652
- previousResponseId: null,
653
- history: [],
654
- messages: [{ id: 'msg-1', sender: 'user', text: 'matching' }],
343
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
344
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
345
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi' } });
346
+ void writer.close();
347
+ t.true(persistenceModule.deleteConversation(id));
348
+ t.false(fs.existsSync(path.join(testDir, `${id}.jsonl`)));
349
+ t.false(fs.existsSync(path.join(testDir, 'last.json')));
350
+ });
351
+ test.serial('subagent_completed and corresponding records omit nestedRunResult', (t) => {
352
+ const id = persistenceModule.generateId();
353
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
354
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
355
+ // 1. Log subagent_completed event
356
+ writer.append({
357
+ type: 'subagent_completed',
358
+ result: {
359
+ agentId: 'sub-agent-1',
360
+ role: 'worker',
361
+ status: 'completed',
362
+ finalText: 'Task resolved successfully',
363
+ filesChanged: ['src/app.ts'],
364
+ toolsUsed: [{ toolName: 'create_file', count: 1 }],
365
+ usage: { prompt_tokens: 120, completion_tokens: 80, total_tokens: 200 },
366
+ nestedRunResult: {
367
+ state: {
368
+ history: [{ role: 'user', content: 'test' }],
369
+ generatedItems: ['item1'],
370
+ },
371
+ },
372
+ },
655
373
  });
656
- persistenceModule.saveConversation({
657
- id: wrongSshId,
658
- createdAt: '2024-01-02T00:00:00.000Z',
659
- updatedAt: '2024-01-02T00:00:00.000Z',
660
- projectPath: '/workspace/alpha',
661
- sshHost: 'other.example.com',
662
- previousResponseId: null,
663
- history: [],
664
- messages: [{ id: 'msg-2', sender: 'user', text: 'wrong ssh' }],
374
+ // 2. Log tool_result event with JSON string containing nestedRunResult
375
+ writer.append({
376
+ type: 'tool_result',
377
+ callId: 'call-subagent-1',
378
+ toolName: 'run_subagent',
379
+ status: 'completed',
380
+ output: JSON.stringify({
381
+ status: 'completed',
382
+ finalText: 'Result text',
383
+ nestedRunResult: { state: { internalStuff: 'hidden' } },
384
+ }),
665
385
  });
666
- persistenceModule.saveConversation({
667
- id: wrongProjectId,
668
- createdAt: '2024-01-01T00:00:00.000Z',
669
- updatedAt: '2024-01-01T00:00:00.000Z',
670
- projectPath: '/workspace/beta',
671
- sshHost: 'myhost.example.com',
672
- previousResponseId: null,
673
- history: [],
674
- messages: [{ id: 'msg-3', sender: 'user', text: 'wrong project' }],
386
+ // 3. Log assistant_final event with nestedRunResult in snapshot
387
+ writer.append({
388
+ type: 'assistant_final',
389
+ message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'all done' },
390
+ finalText: 'all done',
391
+ snapshot: {
392
+ history: [
393
+ {
394
+ role: 'tool',
395
+ type: 'function_call_result',
396
+ name: 'run_subagent',
397
+ output: JSON.stringify({
398
+ status: 'completed',
399
+ finalText: 'Snapshot result text',
400
+ nestedRunResult: { state: { internalStuff: 'hidden' } },
401
+ }),
402
+ },
403
+ ],
404
+ previousResponseId: 'resp-1',
405
+ toolLedger: [
406
+ {
407
+ turnId: 'turn-1',
408
+ callId: 'call-subagent-1',
409
+ toolName: 'run_subagent',
410
+ status: 'completed',
411
+ startedAt: '2026-05-26T00:00:00.000Z',
412
+ output: {
413
+ status: 'completed',
414
+ nestedRunResult: { state: { internalStuff: 'hidden' } },
415
+ },
416
+ },
417
+ ],
418
+ },
675
419
  });
676
- const last = persistenceModule.loadLastConversation('/workspace/alpha', 'myhost.example.com');
420
+ void writer.close();
421
+ // Load raw file contents to check what was written to disk
422
+ const filePath = path.join(testDir, `${id}.jsonl`);
423
+ const lines = fs.readFileSync(filePath, 'utf-8').trim().split('\n');
424
+ // Parse lines to check events
425
+ const envelopes = lines.map((line) => JSON.parse(line));
426
+ // Find subagent_completed
427
+ const completedEnv = envelopes.find((env) => env.event?.type === 'subagent_completed');
428
+ t.truthy(completedEnv);
429
+ t.is(completedEnv.event.result.status, 'completed');
430
+ t.is(completedEnv.event.result.finalText, 'Task resolved successfully');
431
+ t.deepEqual(completedEnv.event.result.filesChanged, ['src/app.ts']);
432
+ t.is(completedEnv.event.result.nestedRunResult, undefined);
433
+ // Find tool_result
434
+ const toolResultEnv = envelopes.find((env) => env.event?.type === 'tool_result');
435
+ t.truthy(toolResultEnv);
436
+ t.is(toolResultEnv.event.toolName, 'run_subagent');
437
+ const parsedOutput = JSON.parse(toolResultEnv.event.output);
438
+ t.is(parsedOutput.status, 'completed');
439
+ t.is(parsedOutput.finalText, 'Result text');
440
+ t.is(parsedOutput.nestedRunResult, undefined);
441
+ // Find assistant_final and check snapshot
442
+ const finalEnv = envelopes.find((env) => env.event?.type === 'assistant_final');
443
+ t.truthy(finalEnv);
444
+ const snapHistoryOutput = JSON.parse(finalEnv.event.snapshot.history[0].output);
445
+ t.is(snapHistoryOutput.status, 'completed');
446
+ t.is(snapHistoryOutput.finalText, 'Snapshot result text');
447
+ t.is(snapHistoryOutput.nestedRunResult, undefined);
448
+ t.is(finalEnv.event.snapshot.toolLedger[0].output.status, 'completed');
449
+ t.is(finalEnv.event.snapshot.toolLedger[0].output.nestedRunResult, undefined);
450
+ });
451
+ test.serial('saveLastConversation: stores per-project last conversation', (t) => {
452
+ const id1 = persistenceModule.generateId();
453
+ const w1 = createConversationLogWriter({ sessionId: id1, dir: testDir, logger: stubLogger });
454
+ w1.init({ id: id1, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/project-a' });
455
+ w1.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi a' } });
456
+ void w1.close();
457
+ const id2 = persistenceModule.generateId();
458
+ const w2 = createConversationLogWriter({ sessionId: id2, dir: testDir, logger: stubLogger });
459
+ w2.init({ id: id2, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/project-b' });
460
+ w2.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'hi b' } });
461
+ void w2.close();
462
+ const lastA = persistenceModule.loadLastConversation('/project-a');
463
+ t.truthy(lastA);
464
+ t.is(lastA.id, id1);
465
+ const lastB = persistenceModule.loadLastConversation('/project-b');
466
+ t.truthy(lastB);
467
+ t.is(lastB.id, id2);
468
+ });
469
+ test.serial('saveLastConversation: stores per-ssh-host last conversation', (t) => {
470
+ const id1 = persistenceModule.generateId();
471
+ const w1 = createConversationLogWriter({ sessionId: id1, dir: testDir, logger: stubLogger });
472
+ w1.init({ id: id1, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/remote', sshHost: 'host-a' });
473
+ w1.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi a' } });
474
+ void w1.close();
475
+ const id2 = persistenceModule.generateId();
476
+ const w2 = createConversationLogWriter({ sessionId: id2, dir: testDir, logger: stubLogger });
477
+ w2.init({ id: id2, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/remote', sshHost: 'host-b' });
478
+ w2.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'hi b' } });
479
+ void w2.close();
480
+ const lastA = persistenceModule.loadLastConversation('/remote', 'host-a');
481
+ t.truthy(lastA);
482
+ t.is(lastA.id, id1);
483
+ const lastB = persistenceModule.loadLastConversation('/remote', 'host-b');
484
+ t.truthy(lastB);
485
+ t.is(lastB.id, id2);
486
+ });
487
+ test.serial('loadLastConversation: falls back to scanning when no last.json entry matches', (t) => {
488
+ const id = persistenceModule.generateId();
489
+ const w = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
490
+ w.init({ id, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/fallback' });
491
+ w.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi' } });
492
+ void w.close();
493
+ // Directly overwrite last.json so the entry has no projectPath
494
+ fs.writeFileSync(path.join(testDir, 'last.json'), JSON.stringify({ entries: [{ id, updatedAt: new Date().toISOString() }] }), 'utf-8');
495
+ const last = persistenceModule.loadLastConversation('/fallback');
677
496
  t.truthy(last);
678
- t.is(last.id, matchingId);
679
- });
680
- test.serial('loadLastConversation: returns null when no conversation matches both project and ssh', (t) => {
681
- persistenceModule.saveConversation({
682
- id: persistenceModule.generateId(),
683
- createdAt: new Date().toISOString(),
684
- updatedAt: new Date().toISOString(),
685
- projectPath: '/workspace/alpha',
686
- sshHost: 'myhost.example.com',
687
- previousResponseId: null,
688
- history: [],
689
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
690
- });
691
- const last = persistenceModule.loadLastConversation('/workspace/alpha', 'otherhost.example.com');
692
- t.is(last, null);
497
+ t.is(last.id, id);
693
498
  });
694
- test.serial('loadConversation: unscoped load (no filters) accepts conversation with sshHost', (t) => {
499
+ test.serial('loadLastConversation: migrates old last.json format', (t) => {
695
500
  const id = persistenceModule.generateId();
696
- const conversation = {
697
- id,
698
- createdAt: new Date().toISOString(),
699
- updatedAt: new Date().toISOString(),
700
- sshHost: 'myhost.example.com',
701
- projectPath: '/workspace/alpha',
702
- previousResponseId: null,
703
- history: [],
704
- messages: [{ id: 'msg-1', sender: 'user', text: 'hello' }],
705
- };
706
- persistenceModule.saveConversation(conversation);
707
- // No filters at all — should pass through
708
- const loaded = persistenceModule.loadConversation(id);
709
- t.truthy(loaded);
710
- t.is(loaded.id, id);
711
- t.is(loaded.sshHost, 'myhost.example.com');
501
+ const w = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
502
+ w.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
503
+ w.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi' } });
504
+ void w.close();
505
+ // Simulate old format
506
+ fs.writeFileSync(path.join(testDir, 'last.json'), JSON.stringify({ id, updatedAt: '2026-05-26T00:00:00.000Z' }), 'utf-8');
507
+ const last = persistenceModule.loadLastConversation();
508
+ t.truthy(last);
509
+ t.is(last.id, id);
712
510
  });
511
+ test.serial('deleteConversation: removes only matching entry from last.json', (t) => {
512
+ const id1 = persistenceModule.generateId();
513
+ const w1 = createConversationLogWriter({ sessionId: id1, dir: testDir, logger: stubLogger });
514
+ w1.init({ id: id1, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/project-a' });
515
+ w1.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi a' } });
516
+ void w1.close();
517
+ const id2 = persistenceModule.generateId();
518
+ const w2 = createConversationLogWriter({ sessionId: id2, dir: testDir, logger: stubLogger });
519
+ w2.init({ id: id2, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/project-b' });
520
+ w2.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'hi b' } });
521
+ void w2.close();
522
+ t.true(persistenceModule.deleteConversation(id1));
523
+ t.false(fs.existsSync(path.join(testDir, `${id1}.jsonl`)));
524
+ t.true(fs.existsSync(path.join(testDir, 'last.json')));
525
+ const lastB = persistenceModule.loadLastConversation('/project-b');
526
+ t.truthy(lastB);
527
+ t.is(lastB.id, id2);
528
+ });
529
+ test.serial('saveLastConversation: updates entry when projectPath changes for same id', (t) => {
530
+ const id = persistenceModule.generateId();
531
+ const w = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
532
+ w.init({ id, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/old-path' });
533
+ w.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hi' } });
534
+ void w.close();
535
+ // Re-open same session with different project path (simulating rotate or manual update)
536
+ const w2 = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
537
+ w2.init({ id, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/new-path' });
538
+ w2.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'hi again' } });
539
+ void w2.close();
540
+ t.is(persistenceModule.loadLastConversation('/old-path'), null);
541
+ const lastNew = persistenceModule.loadLastConversation('/new-path');
542
+ t.truthy(lastNew);
543
+ t.is(lastNew.id, id);
544
+ });
545
+ // Suppress unused-event-import lint
546
+ const _ev = null;
547
+ void _ev;
713
548
  //# sourceMappingURL=conversation-persistence.test.js.map