@qduc/term2 0.7.0 → 0.7.2

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 (509) hide show
  1. package/dist/agent.d.ts +0 -1
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +28 -8
  4. package/dist/agent.js.map +1 -1
  5. package/dist/app.d.ts +3 -3
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js +161 -75
  8. package/dist/app.js.map +1 -1
  9. package/dist/app.large-uncached-warning.test.js.map +1 -1
  10. package/dist/cli.integration.test.d.ts +2 -0
  11. package/dist/cli.integration.test.d.ts.map +1 -0
  12. package/dist/cli.integration.test.js +142 -0
  13. package/dist/cli.integration.test.js.map +1 -0
  14. package/dist/cli.js +47 -12
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/auto-approve-command.d.ts +10 -0
  17. package/dist/commands/auto-approve-command.d.ts.map +1 -0
  18. package/dist/commands/auto-approve-command.js +40 -0
  19. package/dist/commands/auto-approve-command.js.map +1 -0
  20. package/dist/commands/clear-command.d.ts +3 -0
  21. package/dist/commands/clear-command.d.ts.map +1 -0
  22. package/dist/commands/clear-command.js +12 -0
  23. package/dist/commands/clear-command.js.map +1 -0
  24. package/dist/commands/copy-command.d.ts +10 -0
  25. package/dist/commands/copy-command.d.ts.map +1 -0
  26. package/dist/commands/copy-command.js +25 -0
  27. package/dist/commands/copy-command.js.map +1 -0
  28. package/dist/commands/effort-command.d.ts +11 -0
  29. package/dist/commands/effort-command.d.ts.map +1 -0
  30. package/dist/commands/effort-command.js +34 -0
  31. package/dist/commands/effort-command.js.map +1 -0
  32. package/dist/commands/guarded-settings-command.d.ts +14 -0
  33. package/dist/commands/guarded-settings-command.d.ts.map +1 -0
  34. package/dist/commands/guarded-settings-command.js +23 -0
  35. package/dist/commands/guarded-settings-command.js.map +1 -0
  36. package/dist/commands/handoff-command.d.ts +10 -0
  37. package/dist/commands/handoff-command.d.ts.map +1 -0
  38. package/dist/commands/handoff-command.js +17 -0
  39. package/dist/commands/handoff-command.js.map +1 -0
  40. package/dist/commands/mode-commands.d.ts +32 -0
  41. package/dist/commands/mode-commands.d.ts.map +1 -0
  42. package/dist/commands/mode-commands.js +64 -0
  43. package/dist/commands/mode-commands.js.map +1 -0
  44. package/dist/commands/model-command.d.ts +11 -0
  45. package/dist/commands/model-command.d.ts.map +1 -0
  46. package/dist/commands/model-command.js +35 -0
  47. package/dist/commands/model-command.js.map +1 -0
  48. package/dist/commands/quit-command.d.ts +3 -0
  49. package/dist/commands/quit-command.d.ts.map +1 -0
  50. package/dist/commands/quit-command.js +10 -0
  51. package/dist/commands/quit-command.js.map +1 -0
  52. package/dist/commands/retry-command.d.ts +19 -0
  53. package/dist/commands/retry-command.d.ts.map +1 -0
  54. package/dist/commands/retry-command.js +30 -0
  55. package/dist/commands/retry-command.js.map +1 -0
  56. package/dist/commands/undo-command.d.ts +15 -0
  57. package/dist/commands/undo-command.d.ts.map +1 -0
  58. package/dist/commands/undo-command.js +28 -0
  59. package/dist/commands/undo-command.js.map +1 -0
  60. package/dist/commands/usage-command.d.ts +3 -0
  61. package/dist/commands/usage-command.d.ts.map +1 -0
  62. package/dist/commands/usage-command.js +11 -0
  63. package/dist/commands/usage-command.js.map +1 -0
  64. package/dist/components/BottomArea.d.ts +7 -0
  65. package/dist/components/BottomArea.d.ts.map +1 -1
  66. package/dist/components/BottomArea.js +15 -6
  67. package/dist/components/BottomArea.js.map +1 -1
  68. package/dist/components/BottomArea.test.js +36 -4
  69. package/dist/components/BottomArea.test.js.map +1 -1
  70. package/dist/components/Common/MenuContainer.d.ts +1 -3
  71. package/dist/components/Common/MenuContainer.d.ts.map +1 -1
  72. package/dist/components/Common/MenuContainer.js +1 -13
  73. package/dist/components/Common/MenuContainer.js.map +1 -1
  74. package/dist/components/Input/input-width.d.ts +2 -1
  75. package/dist/components/Input/input-width.d.ts.map +1 -1
  76. package/dist/components/Input/input-width.js +6 -2
  77. package/dist/components/Input/input-width.js.map +1 -1
  78. package/dist/components/InputBox.d.ts +2 -0
  79. package/dist/components/InputBox.d.ts.map +1 -1
  80. package/dist/components/InputBox.js +21 -7
  81. package/dist/components/InputBox.js.map +1 -1
  82. package/dist/components/InputBox.test.js +44 -0
  83. package/dist/components/InputBox.test.js.map +1 -1
  84. package/dist/components/LargeUncachedConfirmationPrompt.d.ts +9 -0
  85. package/dist/components/LargeUncachedConfirmationPrompt.d.ts.map +1 -0
  86. package/dist/components/LargeUncachedConfirmationPrompt.js +44 -0
  87. package/dist/components/LargeUncachedConfirmationPrompt.js.map +1 -0
  88. package/dist/components/LargeUncachedConfirmationPrompt.test.d.ts +2 -0
  89. package/dist/components/LargeUncachedConfirmationPrompt.test.d.ts.map +1 -0
  90. package/dist/components/LargeUncachedConfirmationPrompt.test.js +47 -0
  91. package/dist/components/LargeUncachedConfirmationPrompt.test.js.map +1 -0
  92. package/dist/components/MessageList.d.ts +9 -0
  93. package/dist/components/MessageList.d.ts.map +1 -1
  94. package/dist/components/MessageList.js +29 -3
  95. package/dist/components/MessageList.js.map +1 -1
  96. package/dist/components/MessageList.test.js +28 -1
  97. package/dist/components/MessageList.test.js.map +1 -1
  98. package/dist/components/ModelSelectionMenu.d.ts.map +1 -1
  99. package/dist/components/ModelSelectionMenu.js +1 -4
  100. package/dist/components/ModelSelectionMenu.js.map +1 -1
  101. package/dist/components/PathSelectionMenu.d.ts.map +1 -1
  102. package/dist/components/PathSelectionMenu.js +1 -7
  103. package/dist/components/PathSelectionMenu.js.map +1 -1
  104. package/dist/components/SettingsSelectionMenu.d.ts.map +1 -1
  105. package/dist/components/SettingsSelectionMenu.js +1 -12
  106. package/dist/components/SettingsSelectionMenu.js.map +1 -1
  107. package/dist/components/SettingsValueSelectionMenu.d.ts.map +1 -1
  108. package/dist/components/SettingsValueSelectionMenu.js +1 -8
  109. package/dist/components/SettingsValueSelectionMenu.js.map +1 -1
  110. package/dist/components/StatusBar.d.ts +4 -0
  111. package/dist/components/StatusBar.d.ts.map +1 -1
  112. package/dist/components/StatusBar.js +25 -6
  113. package/dist/components/StatusBar.js.map +1 -1
  114. package/dist/components/StatusBar.test.js +23 -0
  115. package/dist/components/StatusBar.test.js.map +1 -1
  116. package/dist/components/UndoSelectionMenu.d.ts.map +1 -1
  117. package/dist/components/UndoSelectionMenu.js +1 -1
  118. package/dist/components/UndoSelectionMenu.js.map +1 -1
  119. package/dist/context/InputContext.d.ts +5 -0
  120. package/dist/context/InputContext.d.ts.map +1 -1
  121. package/dist/context/InputContext.images.test.d.ts +2 -0
  122. package/dist/context/InputContext.images.test.d.ts.map +1 -0
  123. package/dist/context/InputContext.images.test.js +56 -0
  124. package/dist/context/InputContext.images.test.js.map +1 -0
  125. package/dist/context/InputContext.js +3 -2
  126. package/dist/context/InputContext.js.map +1 -1
  127. package/dist/contracts/conversation.d.ts +3 -0
  128. package/dist/contracts/conversation.d.ts.map +1 -1
  129. package/dist/hooks/use-app-commands.d.ts +11 -32
  130. package/dist/hooks/use-app-commands.d.ts.map +1 -1
  131. package/dist/hooks/use-app-commands.js +64 -369
  132. package/dist/hooks/use-app-commands.js.map +1 -1
  133. package/dist/hooks/use-app-commands.test.js +24 -15
  134. package/dist/hooks/use-app-commands.test.js.map +1 -1
  135. package/dist/hooks/use-conversation.clear.test.js +49 -1
  136. package/dist/hooks/use-conversation.clear.test.js.map +1 -1
  137. package/dist/hooks/use-conversation.d.ts +12 -2
  138. package/dist/hooks/use-conversation.d.ts.map +1 -1
  139. package/dist/hooks/use-conversation.js +29 -25
  140. package/dist/hooks/use-conversation.js.map +1 -1
  141. package/dist/hooks/use-conversation.resume.test.js +26 -3
  142. package/dist/hooks/use-conversation.resume.test.js.map +1 -1
  143. package/dist/hooks/use-mode-handlers.d.ts +4 -1
  144. package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
  145. package/dist/hooks/use-mode-handlers.js +6 -1
  146. package/dist/hooks/use-mode-handlers.js.map +1 -1
  147. package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
  148. package/dist/hooks/use-runtime-settings.js +2 -2
  149. package/dist/hooks/use-runtime-settings.js.map +1 -1
  150. package/dist/hooks/use-runtime-settings.test.js +0 -1
  151. package/dist/hooks/use-runtime-settings.test.js.map +1 -1
  152. package/dist/hooks/use-settings-completion.d.ts.map +1 -1
  153. package/dist/hooks/use-settings-completion.js +8 -1
  154. package/dist/hooks/use-settings-completion.js.map +1 -1
  155. package/dist/hooks/use-settings-completion.test.js +4 -0
  156. package/dist/hooks/use-settings-completion.test.js.map +1 -1
  157. package/dist/hooks/use-slash-commands.d.ts.map +1 -1
  158. package/dist/hooks/use-slash-commands.js +10 -0
  159. package/dist/hooks/use-slash-commands.js.map +1 -1
  160. package/dist/hooks/use-slash-commands.test.js +35 -5
  161. package/dist/hooks/use-slash-commands.test.js.map +1 -1
  162. package/dist/hooks/use-terminal-width.d.ts +2 -1
  163. package/dist/hooks/use-terminal-width.d.ts.map +1 -1
  164. package/dist/hooks/use-terminal-width.js +2 -1
  165. package/dist/hooks/use-terminal-width.js.map +1 -1
  166. package/dist/hooks/use-undo-selection.d.ts.map +1 -1
  167. package/dist/hooks/use-undo-selection.js +3 -1
  168. package/dist/hooks/use-undo-selection.js.map +1 -1
  169. package/dist/lib/openai-agent-client.d.ts.map +1 -1
  170. package/dist/lib/openai-agent-client.flex-tier.test.js +8 -3
  171. package/dist/lib/openai-agent-client.flex-tier.test.js.map +1 -1
  172. package/dist/lib/openai-agent-client.js +50 -24
  173. package/dist/lib/openai-agent-client.js.map +1 -1
  174. package/dist/lib/tool-execution-limiter.d.ts +16 -0
  175. package/dist/lib/tool-execution-limiter.d.ts.map +1 -0
  176. package/dist/lib/tool-execution-limiter.js +112 -0
  177. package/dist/lib/tool-execution-limiter.js.map +1 -0
  178. package/dist/lib/tool-execution-limiter.test.d.ts +2 -0
  179. package/dist/lib/tool-execution-limiter.test.d.ts.map +1 -0
  180. package/dist/lib/tool-execution-limiter.test.js +70 -0
  181. package/dist/lib/tool-execution-limiter.test.js.map +1 -0
  182. package/dist/lib/tool-invoke.test.js +8 -8
  183. package/dist/lib/tool-invoke.test.js.map +1 -1
  184. package/dist/lib/tool-selection-policy.d.ts.map +1 -1
  185. package/dist/lib/tool-selection-policy.js +43 -2
  186. package/dist/lib/tool-selection-policy.js.map +1 -1
  187. package/dist/lib/tool-selection-policy.test.js +39 -0
  188. package/dist/lib/tool-selection-policy.test.js.map +1 -1
  189. package/dist/prompts/gpt-5-modern.md +105 -97
  190. package/dist/prompts/gpt-5.5.md +106 -0
  191. package/dist/prompts/orchestrator.md +2 -5
  192. package/dist/prompts/prompt-profiles.js +1 -1
  193. package/dist/prompts/prompt-profiles.js.map +1 -1
  194. package/dist/prompts/prompt-profiles.ts +1 -1
  195. package/dist/prompts/prompt-selector.test.js +1 -1
  196. package/dist/prompts/prompt-selector.test.js.map +1 -1
  197. package/dist/prompts/prompt-selector.test.ts +1 -1
  198. package/dist/prompts/search-via-shell.d.ts.map +1 -1
  199. package/dist/prompts/search-via-shell.js +0 -1
  200. package/dist/prompts/search-via-shell.js.map +1 -1
  201. package/dist/prompts/search-via-shell.ts +0 -1
  202. package/dist/prompts/shell-auto-approval.d.ts +2 -2
  203. package/dist/prompts/shell-auto-approval.d.ts.map +1 -1
  204. package/dist/prompts/shell-auto-approval.js +6 -6
  205. package/dist/prompts/shell-auto-approval.js.map +1 -1
  206. package/dist/prompts/shell-auto-approval.ts +6 -6
  207. package/dist/prompts/simple-prev.md +55 -0
  208. package/dist/prompts/simple.md +31 -53
  209. package/dist/prompts/subagent-delegation.js +2 -2
  210. package/dist/prompts/subagent-delegation.js.map +1 -1
  211. package/dist/prompts/subagent-delegation.ts +2 -2
  212. package/dist/providers/codex-responses-model.d.ts +5 -3
  213. package/dist/providers/codex-responses-model.d.ts.map +1 -1
  214. package/dist/providers/codex-responses-model.js +76 -20
  215. package/dist/providers/codex-responses-model.js.map +1 -1
  216. package/dist/providers/codex-responses-model.test.js +242 -1
  217. package/dist/providers/codex-responses-model.test.js.map +1 -1
  218. package/dist/providers/codex.provider.d.ts +0 -2
  219. package/dist/providers/codex.provider.d.ts.map +1 -1
  220. package/dist/providers/codex.provider.js +2 -6
  221. package/dist/providers/codex.provider.js.map +1 -1
  222. package/dist/providers/codex.provider.test.js +4 -31
  223. package/dist/providers/codex.provider.test.js.map +1 -1
  224. package/dist/providers/custom-provider-adapter.test.js +3 -3
  225. package/dist/providers/custom-provider-adapter.test.js.map +1 -1
  226. package/dist/providers/fallback-responses-model.d.ts.map +1 -1
  227. package/dist/providers/fallback-responses-model.js +160 -83
  228. package/dist/providers/fallback-responses-model.js.map +1 -1
  229. package/dist/providers/fallback-responses-model.test.js +318 -0
  230. package/dist/providers/fallback-responses-model.test.js.map +1 -1
  231. package/dist/providers/openai-compatible-lazy.d.ts +4 -0
  232. package/dist/providers/openai-compatible-lazy.d.ts.map +1 -0
  233. package/dist/providers/openai-compatible-lazy.js +54 -0
  234. package/dist/providers/openai-compatible-lazy.js.map +1 -0
  235. package/dist/providers/openai-compatible.provider.d.ts +2 -2
  236. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  237. package/dist/providers/openai-compatible.provider.js +3 -3
  238. package/dist/providers/openai-compatible.provider.js.map +1 -1
  239. package/dist/providers/openai-compatible.provider.test.js +31 -0
  240. package/dist/providers/openai-compatible.provider.test.js.map +1 -1
  241. package/dist/providers/openai.provider.js +5 -3
  242. package/dist/providers/openai.provider.js.map +1 -1
  243. package/dist/providers/timed-responses-ws-model.d.ts +20 -0
  244. package/dist/providers/timed-responses-ws-model.d.ts.map +1 -0
  245. package/dist/providers/timed-responses-ws-model.js +381 -0
  246. package/dist/providers/timed-responses-ws-model.js.map +1 -0
  247. package/dist/providers/timed-responses-ws-model.test.d.ts +2 -0
  248. package/dist/providers/timed-responses-ws-model.test.d.ts.map +1 -0
  249. package/dist/providers/timed-responses-ws-model.test.js +441 -0
  250. package/dist/providers/timed-responses-ws-model.test.js.map +1 -0
  251. package/dist/providers/timed-ws-connection.d.ts +26 -0
  252. package/dist/providers/timed-ws-connection.d.ts.map +1 -0
  253. package/dist/providers/timed-ws-connection.js +156 -0
  254. package/dist/providers/timed-ws-connection.js.map +1 -0
  255. package/dist/providers/timed-ws-connection.test.d.ts +2 -0
  256. package/dist/providers/timed-ws-connection.test.d.ts.map +1 -0
  257. package/dist/providers/timed-ws-connection.test.js +223 -0
  258. package/dist/providers/timed-ws-connection.test.js.map +1 -0
  259. package/dist/providers/timed-ws-timeouts.d.ts +6 -0
  260. package/dist/providers/timed-ws-timeouts.d.ts.map +1 -0
  261. package/dist/providers/timed-ws-timeouts.js +6 -0
  262. package/dist/providers/timed-ws-timeouts.js.map +1 -0
  263. package/dist/services/command-message-streaming.d.ts.map +1 -1
  264. package/dist/services/command-message-streaming.js +8 -3
  265. package/dist/services/command-message-streaming.js.map +1 -1
  266. package/dist/services/conversation-clear-save.test.js +4 -5
  267. package/dist/services/conversation-clear-save.test.js.map +1 -1
  268. package/dist/services/conversation-events.d.ts +4 -0
  269. package/dist/services/conversation-events.d.ts.map +1 -1
  270. package/dist/services/conversation-history-repair.d.ts.map +1 -1
  271. package/dist/services/conversation-history-repair.js +81 -26
  272. package/dist/services/conversation-history-repair.js.map +1 -1
  273. package/dist/services/conversation-integration.test.js +109 -0
  274. package/dist/services/conversation-integration.test.js.map +1 -1
  275. package/dist/services/conversation-log-events.d.ts +19 -10
  276. package/dist/services/conversation-log-events.d.ts.map +1 -1
  277. package/dist/services/conversation-log-events.js +1 -1
  278. package/dist/services/conversation-log-events.js.map +1 -1
  279. package/dist/services/conversation-log-writer.d.ts.map +1 -1
  280. package/dist/services/conversation-log-writer.js +2 -3
  281. package/dist/services/conversation-log-writer.js.map +1 -1
  282. package/dist/services/conversation-persistence-types.d.ts +32 -0
  283. package/dist/services/conversation-persistence-types.d.ts.map +1 -1
  284. package/dist/services/conversation-persistence.d.ts +7 -1
  285. package/dist/services/conversation-persistence.d.ts.map +1 -1
  286. package/dist/services/conversation-persistence.js +95 -44
  287. package/dist/services/conversation-persistence.js.map +1 -1
  288. package/dist/services/conversation-persistence.test.js +551 -107
  289. package/dist/services/conversation-persistence.test.js.map +1 -1
  290. package/dist/services/conversation-replay.d.ts.map +1 -1
  291. package/dist/services/conversation-replay.js +234 -11
  292. package/dist/services/conversation-replay.js.map +1 -1
  293. package/dist/services/conversation-replay.test.js +426 -31
  294. package/dist/services/conversation-replay.test.js.map +1 -1
  295. package/dist/services/conversation-result-builder.d.ts +2 -0
  296. package/dist/services/conversation-result-builder.d.ts.map +1 -1
  297. package/dist/services/conversation-result-builder.js +4 -0
  298. package/dist/services/conversation-result-builder.js.map +1 -1
  299. package/dist/services/conversation-result-builder.test.js +126 -0
  300. package/dist/services/conversation-result-builder.test.js.map +1 -1
  301. package/dist/services/conversation-retry-policy.d.ts.map +1 -1
  302. package/dist/services/conversation-retry-policy.js +3 -0
  303. package/dist/services/conversation-retry-policy.js.map +1 -1
  304. package/dist/services/conversation-retry-policy.test.js +4 -0
  305. package/dist/services/conversation-retry-policy.test.js.map +1 -1
  306. package/dist/services/conversation-service.d.ts +3 -3
  307. package/dist/services/conversation-service.d.ts.map +1 -1
  308. package/dist/services/conversation-service.js +0 -3
  309. package/dist/services/conversation-service.js.map +1 -1
  310. package/dist/services/conversation-session.d.ts +10 -7
  311. package/dist/services/conversation-session.d.ts.map +1 -1
  312. package/dist/services/conversation-session.js +283 -86
  313. package/dist/services/conversation-session.js.map +1 -1
  314. package/dist/services/conversation-store.d.ts +16 -13
  315. package/dist/services/conversation-store.d.ts.map +1 -1
  316. package/dist/services/conversation-store.js +62 -191
  317. package/dist/services/conversation-store.js.map +1 -1
  318. package/dist/services/conversation-store.merge-modes.test.d.ts +2 -0
  319. package/dist/services/conversation-store.merge-modes.test.d.ts.map +1 -0
  320. package/dist/services/conversation-store.merge-modes.test.js +161 -0
  321. package/dist/services/conversation-store.merge-modes.test.js.map +1 -0
  322. package/dist/services/conversation-store.test.js +157 -294
  323. package/dist/services/conversation-store.test.js.map +1 -1
  324. package/dist/services/conversation-turn-items.d.ts +5 -0
  325. package/dist/services/conversation-turn-items.d.ts.map +1 -0
  326. package/dist/services/conversation-turn-items.js +351 -0
  327. package/dist/services/conversation-turn-items.js.map +1 -0
  328. package/dist/services/conversation-turn-items.test.d.ts +2 -0
  329. package/dist/services/conversation-turn-items.test.d.ts.map +1 -0
  330. package/dist/services/conversation-turn-items.test.js +68 -0
  331. package/dist/services/conversation-turn-items.test.js.map +1 -0
  332. package/dist/services/large-uncached-input-guard.d.ts.map +1 -1
  333. package/dist/services/large-uncached-input-guard.js +4 -3
  334. package/dist/services/large-uncached-input-guard.js.map +1 -1
  335. package/dist/services/large-uncached-input-guard.test.js +22 -0
  336. package/dist/services/large-uncached-input-guard.test.js.map +1 -1
  337. package/dist/services/notification-service.d.ts +22 -0
  338. package/dist/services/notification-service.d.ts.map +1 -0
  339. package/dist/services/notification-service.js +68 -0
  340. package/dist/services/notification-service.js.map +1 -0
  341. package/dist/services/notification-service.test.d.ts +2 -0
  342. package/dist/services/notification-service.test.d.ts.map +1 -0
  343. package/dist/services/notification-service.test.js +78 -0
  344. package/dist/services/notification-service.test.js.map +1 -0
  345. package/dist/services/provider-traffic.d.ts.map +1 -1
  346. package/dist/services/provider-traffic.js +1 -0
  347. package/dist/services/provider-traffic.js.map +1 -1
  348. package/dist/services/provider-traffic.test.js +51 -0
  349. package/dist/services/provider-traffic.test.js.map +1 -1
  350. package/dist/services/settings-schema.d.ts +16 -1
  351. package/dist/services/settings-schema.d.ts.map +1 -1
  352. package/dist/services/settings-schema.js +42 -22
  353. package/dist/services/settings-schema.js.map +1 -1
  354. package/dist/services/settings-schema.test.js +10 -0
  355. package/dist/services/settings-schema.test.js.map +1 -1
  356. package/dist/services/settings-service.js +2 -2
  357. package/dist/services/settings-service.js.map +1 -1
  358. package/dist/services/settings-sources.d.ts.map +1 -1
  359. package/dist/services/settings-sources.js +1 -0
  360. package/dist/services/settings-sources.js.map +1 -1
  361. package/dist/services/settings-sources.test.js +2 -0
  362. package/dist/services/settings-sources.test.js.map +1 -1
  363. package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
  364. package/dist/services/subagents/subagent-manager.js +32 -30
  365. package/dist/services/subagents/subagent-manager.js.map +1 -1
  366. package/dist/services/subagents/subagent-manager.test.js +8 -12
  367. package/dist/services/subagents/subagent-manager.test.js.map +1 -1
  368. package/dist/services/subagents/subagent-session.d.ts +1 -1
  369. package/dist/services/subagents/subagent-session.d.ts.map +1 -1
  370. package/dist/services/subagents/subagent-session.js +2 -2
  371. package/dist/services/subagents/subagent-session.js.map +1 -1
  372. package/dist/services/subagents/subagent-session.test.js +3 -3
  373. package/dist/services/subagents/subagent-session.test.js.map +1 -1
  374. package/dist/services/subagents/types.d.ts +2 -1
  375. package/dist/services/subagents/types.d.ts.map +1 -1
  376. package/dist/services/terminal-result-collector.d.ts.map +1 -1
  377. package/dist/services/terminal-result-collector.js +43 -0
  378. package/dist/services/terminal-result-collector.js.map +1 -1
  379. package/dist/services/terminal-result-collector.test.js +44 -0
  380. package/dist/services/terminal-result-collector.test.js.map +1 -1
  381. package/dist/services/tool-execution-ledger.d.ts +6 -0
  382. package/dist/services/tool-execution-ledger.d.ts.map +1 -1
  383. package/dist/services/tool-execution-ledger.js +90 -25
  384. package/dist/services/tool-execution-ledger.js.map +1 -1
  385. package/dist/services/tool-execution-ledger.test.js +177 -2
  386. package/dist/services/tool-execution-ledger.test.js.map +1 -1
  387. package/dist/tools/apply-patch.d.ts.map +1 -1
  388. package/dist/tools/apply-patch.js +41 -23
  389. package/dist/tools/apply-patch.js.map +1 -1
  390. package/dist/tools/apply-patch.test.js +6 -6
  391. package/dist/tools/apply-patch.test.js.map +1 -1
  392. package/dist/tools/code-context.js +1 -1
  393. package/dist/tools/code-context.js.map +1 -1
  394. package/dist/tools/code-context.test.js +278 -0
  395. package/dist/tools/code-context.test.js.map +1 -1
  396. package/dist/tools/edit-healing.d.ts.map +1 -1
  397. package/dist/tools/edit-healing.js +9 -3
  398. package/dist/tools/edit-healing.js.map +1 -1
  399. package/dist/tools/language-providers.d.ts.map +1 -1
  400. package/dist/tools/language-providers.js +379 -0
  401. package/dist/tools/language-providers.js.map +1 -1
  402. package/dist/tools/languages/cpp.d.ts +8 -0
  403. package/dist/tools/languages/cpp.d.ts.map +1 -0
  404. package/dist/tools/languages/cpp.js +306 -0
  405. package/dist/tools/languages/cpp.js.map +1 -0
  406. package/dist/tools/languages/csharp.d.ts +5 -0
  407. package/dist/tools/languages/csharp.d.ts.map +1 -0
  408. package/dist/tools/languages/csharp.js +225 -0
  409. package/dist/tools/languages/csharp.js.map +1 -0
  410. package/dist/tools/languages/go.d.ts +7 -0
  411. package/dist/tools/languages/go.d.ts.map +1 -0
  412. package/dist/tools/languages/go.js +223 -0
  413. package/dist/tools/languages/go.js.map +1 -0
  414. package/dist/tools/languages/index.d.ts +6 -0
  415. package/dist/tools/languages/index.d.ts.map +1 -0
  416. package/dist/tools/languages/index.js +32 -0
  417. package/dist/tools/languages/index.js.map +1 -0
  418. package/dist/tools/languages/java.d.ts +5 -0
  419. package/dist/tools/languages/java.d.ts.map +1 -0
  420. package/dist/tools/languages/java.js +183 -0
  421. package/dist/tools/languages/java.js.map +1 -0
  422. package/dist/tools/languages/json.d.ts +3 -0
  423. package/dist/tools/languages/json.d.ts.map +1 -0
  424. package/dist/tools/languages/json.js +40 -0
  425. package/dist/tools/languages/json.js.map +1 -0
  426. package/dist/tools/languages/php.d.ts +8 -0
  427. package/dist/tools/languages/php.d.ts.map +1 -0
  428. package/dist/tools/languages/php.js +139 -0
  429. package/dist/tools/languages/php.js.map +1 -0
  430. package/dist/tools/languages/python.d.ts +7 -0
  431. package/dist/tools/languages/python.d.ts.map +1 -0
  432. package/dist/tools/languages/python.js +189 -0
  433. package/dist/tools/languages/python.js.map +1 -0
  434. package/dist/tools/languages/ruby.d.ts +8 -0
  435. package/dist/tools/languages/ruby.d.ts.map +1 -0
  436. package/dist/tools/languages/ruby.js +94 -0
  437. package/dist/tools/languages/ruby.js.map +1 -0
  438. package/dist/tools/languages/rust.d.ts +7 -0
  439. package/dist/tools/languages/rust.d.ts.map +1 -0
  440. package/dist/tools/languages/rust.js +186 -0
  441. package/dist/tools/languages/rust.js.map +1 -0
  442. package/dist/tools/languages/types.d.ts +39 -0
  443. package/dist/tools/languages/types.d.ts.map +1 -0
  444. package/dist/tools/languages/types.js +2 -0
  445. package/dist/tools/languages/types.js.map +1 -0
  446. package/dist/tools/languages/typescript.d.ts +4 -0
  447. package/dist/tools/languages/typescript.d.ts.map +1 -0
  448. package/dist/tools/languages/typescript.js +94 -0
  449. package/dist/tools/languages/typescript.js.map +1 -0
  450. package/dist/tools/languages/utils.d.ts +14 -0
  451. package/dist/tools/languages/utils.d.ts.map +1 -0
  452. package/dist/tools/languages/utils.js +73 -0
  453. package/dist/tools/languages/utils.js.map +1 -0
  454. package/dist/tools/run-subagent.d.ts +0 -1
  455. package/dist/tools/run-subagent.d.ts.map +1 -1
  456. package/dist/tools/run-subagent.js +1 -8
  457. package/dist/tools/run-subagent.js.map +1 -1
  458. package/dist/tools/run-subagent.test.js +0 -18
  459. package/dist/tools/run-subagent.test.js.map +1 -1
  460. package/dist/tools/shell.d.ts.map +1 -1
  461. package/dist/tools/shell.js +13 -1
  462. package/dist/tools/shell.js.map +1 -1
  463. package/dist/tools/web-fetch.d.ts.map +1 -1
  464. package/dist/tools/web-fetch.js +1 -1
  465. package/dist/tools/web-fetch.js.map +1 -1
  466. package/dist/utils/clipboard.d.ts +8 -0
  467. package/dist/utils/clipboard.d.ts.map +1 -1
  468. package/dist/utils/clipboard.js +50 -4
  469. package/dist/utils/clipboard.js.map +1 -1
  470. package/dist/utils/clipboard.test.js +152 -2
  471. package/dist/utils/clipboard.test.js.map +1 -1
  472. package/dist/utils/conversation-event-handler.d.ts.map +1 -1
  473. package/dist/utils/conversation-event-handler.js +3 -0
  474. package/dist/utils/conversation-event-handler.js.map +1 -1
  475. package/dist/utils/conversation-event-handler.test.js +13 -0
  476. package/dist/utils/conversation-event-handler.test.js.map +1 -1
  477. package/dist/utils/extract-command-messages.d.ts.map +1 -1
  478. package/dist/utils/extract-command-messages.js +8 -4
  479. package/dist/utils/extract-command-messages.js.map +1 -1
  480. package/dist/utils/message-utils.d.ts +7 -0
  481. package/dist/utils/message-utils.d.ts.map +1 -0
  482. package/dist/utils/message-utils.js +31 -0
  483. package/dist/utils/message-utils.js.map +1 -0
  484. package/dist/utils/resume-list.d.ts +15 -0
  485. package/dist/utils/resume-list.d.ts.map +1 -0
  486. package/dist/utils/resume-list.js +68 -0
  487. package/dist/utils/resume-list.js.map +1 -0
  488. package/dist/utils/resume-list.test.d.ts +2 -0
  489. package/dist/utils/resume-list.test.d.ts.map +1 -0
  490. package/dist/utils/resume-list.test.js +89 -0
  491. package/dist/utils/resume-list.test.js.map +1 -0
  492. package/dist/utils/settings-command.d.ts.map +1 -1
  493. package/dist/utils/settings-command.js +11 -0
  494. package/dist/utils/settings-command.js.map +1 -1
  495. package/dist/utils/token-usage.d.ts +3 -1
  496. package/dist/utils/token-usage.d.ts.map +1 -1
  497. package/dist/utils/token-usage.js +4 -2
  498. package/dist/utils/token-usage.js.map +1 -1
  499. package/dist/utils/tty-osc.d.ts +27 -0
  500. package/dist/utils/tty-osc.d.ts.map +1 -0
  501. package/dist/utils/tty-osc.js +66 -0
  502. package/dist/utils/tty-osc.js.map +1 -0
  503. package/dist/utils/tty-osc.test.d.ts +2 -0
  504. package/dist/utils/tty-osc.test.d.ts.map +1 -0
  505. package/dist/utils/tty-osc.test.js +130 -0
  506. package/dist/utils/tty-osc.test.js.map +1 -0
  507. package/package.json +3 -3
  508. package/readme.md +24 -9
  509. package/dist/app.large-uncached-warning.test.js +0 -15
@@ -4,6 +4,8 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import * as persistenceModule from './conversation-persistence.js';
6
6
  import { createConversationLogWriter, LockConflictError } from './conversation-log-writer.js';
7
+ import { ConversationSession } from './conversation-session.js';
8
+ import { createMockSettingsService } from './settings-service.mock.js';
7
9
  let testDir = '';
8
10
  function cleanupAll() {
9
11
  if (testDir && fs.existsSync(testDir)) {
@@ -21,6 +23,32 @@ const stubLogger = {
21
23
  function emptySnapshot() {
22
24
  return { history: [], previousResponseId: null, toolLedger: [] };
23
25
  }
26
+ function assistantTurn(text, previousResponseId = 'r1') {
27
+ return {
28
+ type: 'assistant_turn',
29
+ turn: { items: [{ type: 'assistant_text', text }] },
30
+ state: { previousResponseId },
31
+ };
32
+ }
33
+ class MockStream {
34
+ events;
35
+ completed = Promise.resolve(undefined);
36
+ lastResponseId = 'resp-v3';
37
+ interruptions = [];
38
+ state = {};
39
+ newItems = [];
40
+ history = [];
41
+ output = [];
42
+ finalOutput = 'Done.';
43
+ constructor(events = []) {
44
+ this.events = events;
45
+ }
46
+ async *[Symbol.asyncIterator]() {
47
+ for (const event of this.events) {
48
+ yield event;
49
+ }
50
+ }
51
+ }
24
52
  test.beforeEach(() => {
25
53
  testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'term2-conversations-test-'));
26
54
  persistenceModule.setConversationsDirForTest(testDir);
@@ -56,19 +84,9 @@ test.serial('writer + loadConversation: round-trips a basic conversation', (t) =
56
84
  });
57
85
  writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'hello' } });
58
86
  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
- },
87
+ type: 'assistant_turn',
88
+ turn: { items: [{ type: 'assistant_text', text: 'hi there' }] },
89
+ state: { previousResponseId: 'resp-1', model: 'gpt-4o', provider: 'openai' },
72
90
  });
73
91
  void writer.close();
74
92
  const restored = persistenceModule.loadConversation(id);
@@ -89,16 +107,7 @@ test.serial('replay: mid-turn crash with tool_started inserts recovery notice',
89
107
  writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
90
108
  // First turn completes cleanly
91
109
  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
- });
110
+ writer.append(assistantTurn('done'));
102
111
  // Second turn: user submits, tool starts, then crash
103
112
  writer.append({ type: 'user_message', message: { id: 'u2', sender: 'user', text: 'more' } });
104
113
  writer.append({ type: 'tool_started', toolCallId: 'call-1', toolName: 'shell', arguments: {} });
@@ -106,13 +115,10 @@ test.serial('replay: mid-turn crash with tool_started inserts recovery notice',
106
115
  const restored = persistenceModule.loadConversation(id);
107
116
  t.truthy(restored);
108
117
  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
118
  // The interrupted system message is on UI messages
113
119
  t.true(restored.messages.some((m) => m.sender === 'system' && String(m.text).includes('interrupted')));
114
120
  });
115
- test.serial('replay: user_message only with no assistant_final flags interruption', (t) => {
121
+ test.serial('replay: user_message only with no assistant_turn flags interruption', (t) => {
116
122
  const id = persistenceModule.generateId();
117
123
  const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
118
124
  writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
@@ -129,10 +135,9 @@ test.serial('replay: settings_changed updates restored model', (t) => {
129
135
  writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z', model: 'gpt-4o' });
130
136
  writer.append({ type: 'settings_changed', key: 'agent.model', value: 'gpt-5' });
131
137
  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' },
138
+ type: 'assistant_turn',
139
+ turn: { items: [{ type: 'assistant_text', text: 'ok' }] },
140
+ state: { previousResponseId: null, model: 'gpt-5' },
136
141
  });
137
142
  void writer.close();
138
143
  const restored = persistenceModule.loadConversation(id);
@@ -143,27 +148,9 @@ test.serial('replay: undo with snapshot replaces state', (t) => {
143
148
  const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
144
149
  writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
145
150
  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
- });
151
+ writer.append(assistantTurn('A'));
156
152
  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
- });
153
+ writer.append(assistantTurn('B', 'r2'));
167
154
  writer.append({ type: 'undo', removedUserTurns: 1, snapshot: emptySnapshot() });
168
155
  void writer.close();
169
156
  const restored = persistenceModule.loadConversation(id);
@@ -180,18 +167,17 @@ test.serial('replay: corrupt line is skipped', (t) => {
180
167
  ts: '2026-05-26T00:00:00.000Z',
181
168
  event: { type: 'session_init', id, createdAt: '2026-05-26T00:00:00.000Z' },
182
169
  });
183
- const goodFinal = JSON.stringify({
184
- v: 1,
170
+ const goodTurn = JSON.stringify({
171
+ v: 3,
185
172
  seq: 3,
186
173
  ts: '2026-05-26T00:00:01.000Z',
187
174
  event: {
188
- type: 'assistant_final',
189
- message: { id: 'b1', sender: 'bot', status: 'finalized', text: 'ok' },
190
- finalText: 'ok',
191
- snapshot: { history: [], previousResponseId: 'r1', toolLedger: [] },
175
+ type: 'assistant_turn',
176
+ turn: { items: [{ type: 'assistant_text', text: 'ok' }] },
177
+ state: { previousResponseId: 'r1' },
192
178
  },
193
179
  });
194
- fs.writeFileSync(filePath, `${goodInit}\n{not json\n${goodFinal}\n`, 'utf-8');
180
+ fs.writeFileSync(filePath, `${goodInit}\n{not json\n${goodTurn}\n`, 'utf-8');
195
181
  const restored = persistenceModule.loadConversation(id);
196
182
  t.is(restored.previousResponseId, 'r1');
197
183
  });
@@ -217,16 +203,7 @@ test.serial('forkConversation: copies the source jsonl to a new id', (t) => {
217
203
  const dstId = persistenceModule.generateId();
218
204
  const writer = createConversationLogWriter({ sessionId: srcId, dir: testDir, logger: stubLogger });
219
205
  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: [],
228
- },
229
- });
206
+ writer.append(assistantTurn('A'));
230
207
  void writer.close();
231
208
  t.true(persistenceModule.forkConversation(srcId, dstId));
232
209
  const restored = persistenceModule.loadConversation(dstId);
@@ -251,6 +228,32 @@ test.serial('listConversations: lists sessions sorted by mtime desc', (t) => {
251
228
  t.is(list[0].id, id2);
252
229
  t.is(list[0].projectPath, '/p2');
253
230
  });
231
+ test.serial('listConversations: filters sessions by workspace and ssh host', (t) => {
232
+ const id1 = persistenceModule.generateId();
233
+ const w1 = createConversationLogWriter({ sessionId: id1, dir: testDir, logger: stubLogger });
234
+ w1.init({ id: id1, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/workspace/p1' });
235
+ void w1.close();
236
+ const id2 = persistenceModule.generateId();
237
+ const w2 = createConversationLogWriter({ sessionId: id2, dir: testDir, logger: stubLogger });
238
+ w2.init({ id: id2, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/workspace/p2' });
239
+ void w2.close();
240
+ const id3 = persistenceModule.generateId();
241
+ const w3 = createConversationLogWriter({ sessionId: id3, dir: testDir, logger: stubLogger });
242
+ w3.init({ id: id3, createdAt: '2026-05-26T00:00:00.000Z', projectPath: '/workspace/p1', sshHost: 'host1' });
243
+ void w3.close();
244
+ // Filter for local /workspace/p1
245
+ const listP1Local = persistenceModule.listConversations('/workspace/p1');
246
+ t.is(listP1Local.length, 1);
247
+ t.is(listP1Local[0].id, id1);
248
+ // Filter for remote /workspace/p1 on host1
249
+ const listP1Host1 = persistenceModule.listConversations('/workspace/p1', 'host1');
250
+ t.is(listP1Host1.length, 1);
251
+ t.is(listP1Host1[0].id, id3);
252
+ // Filter for /workspace/p2
253
+ const listP2Local = persistenceModule.listConversations('/workspace/p2');
254
+ t.is(listP2Local.length, 1);
255
+ t.is(listP2Local[0].id, id2);
256
+ });
254
257
  test.serial('loadConversation: returns null when expected project path differs', (t) => {
255
258
  const id = persistenceModule.generateId();
256
259
  const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
@@ -315,19 +318,25 @@ test.serial('hasConversationContent: returns true for user_message', (t) => {
315
318
  void writer.close();
316
319
  t.true(persistenceModule.hasConversationContent(id));
317
320
  });
318
- test.serial('hasConversationContent: returns true for assistant_final', (t) => {
321
+ test.serial('hasConversationContent: returns true for assistant_turn', (t) => {
319
322
  const id = persistenceModule.generateId();
320
323
  const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
321
324
  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
- });
325
+ writer.append(assistantTurn('hello'));
328
326
  void writer.close();
329
327
  t.true(persistenceModule.hasConversationContent(id));
330
328
  });
329
+ test.serial('hasConversationContent: ignores unsupported assistant_final events', (t) => {
330
+ const id = persistenceModule.generateId();
331
+ const filePath = path.join(testDir, `${id}.jsonl`);
332
+ fs.writeFileSync(filePath, JSON.stringify({
333
+ v: 1,
334
+ seq: 1,
335
+ ts: '2026-05-26T00:00:00.000Z',
336
+ event: { type: 'assistant_final', message: { id: 'a1', sender: 'bot', text: 'legacy' } },
337
+ }) + '\n', 'utf-8');
338
+ t.false(persistenceModule.hasConversationContent(id));
339
+ });
331
340
  test.serial('hasConversationContent: skips corrupt lines and finds content', (t) => {
332
341
  const id = persistenceModule.generateId();
333
342
  const filePath = path.join(testDir, `${id}.jsonl`);
@@ -383,39 +392,26 @@ test.serial('subagent_completed and corresponding records omit nestedRunResult',
383
392
  nestedRunResult: { state: { internalStuff: 'hidden' } },
384
393
  }),
385
394
  });
386
- // 3. Log assistant_final event with nestedRunResult in snapshot
395
+ // 3. Log assistant_turn event with nestedRunResult in turn items
387
396
  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: [
397
+ type: 'assistant_turn',
398
+ turn: {
399
+ items: [
393
400
  {
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',
401
+ type: 'tool_result',
408
402
  callId: 'call-subagent-1',
409
403
  toolName: 'run_subagent',
410
404
  status: 'completed',
411
- startedAt: '2026-05-26T00:00:00.000Z',
412
- output: {
405
+ output: JSON.stringify({
413
406
  status: 'completed',
407
+ finalText: 'Turn result text',
414
408
  nestedRunResult: { state: { internalStuff: 'hidden' } },
415
- },
409
+ }),
416
410
  },
411
+ { type: 'assistant_text', text: 'all done' },
417
412
  ],
418
413
  },
414
+ state: { previousResponseId: 'resp-1' },
419
415
  });
420
416
  void writer.close();
421
417
  // Load raw file contents to check what was written to disk
@@ -438,15 +434,13 @@ test.serial('subagent_completed and corresponding records omit nestedRunResult',
438
434
  t.is(parsedOutput.status, 'completed');
439
435
  t.is(parsedOutput.finalText, 'Result text');
440
436
  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);
437
+ // Find assistant_turn and check turn items
438
+ const turnEnv = envelopes.find((env) => env.event?.type === 'assistant_turn');
439
+ t.truthy(turnEnv);
440
+ const turnOutput = JSON.parse(turnEnv.event.turn.items[0].output);
441
+ t.is(turnOutput.status, 'completed');
442
+ t.is(turnOutput.finalText, 'Turn result text');
443
+ t.is(turnOutput.nestedRunResult, undefined);
450
444
  });
451
445
  test.serial('saveLastConversation: stores per-project last conversation', (t) => {
452
446
  const id1 = persistenceModule.generateId();
@@ -545,4 +539,454 @@ test.serial('saveLastConversation: updates entry when projectPath changes for sa
545
539
  // Suppress unused-event-import lint
546
540
  const _ev = null;
547
541
  void _ev;
542
+ test.serial('writer + loadConversation: round-trips a v2 conversation with assistant_turn', (t) => {
543
+ const id = persistenceModule.generateId();
544
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
545
+ writer.init({
546
+ id,
547
+ createdAt: '2026-05-26T00:00:00.000Z',
548
+ projectPath: '/workspace/y',
549
+ model: 'gpt-4o',
550
+ provider: 'openai',
551
+ });
552
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'run tool' } });
553
+ writer.append({ type: 'tool_started', toolCallId: 'call-v2', toolName: 'shell', arguments: 'ls' });
554
+ writer.append({
555
+ type: 'command_message',
556
+ message: {
557
+ id: 'cmd-v2',
558
+ sender: 'command',
559
+ status: 'completed',
560
+ command: 'ls',
561
+ output: 'file.txt',
562
+ success: true,
563
+ callId: 'call-v2',
564
+ toolName: 'shell',
565
+ },
566
+ });
567
+ writer.append({
568
+ type: 'assistant_turn',
569
+ turn: {
570
+ items: [
571
+ { type: 'reasoning', text: 'thinking about ls' },
572
+ { type: 'tool_call', callId: 'call-v2', toolName: 'shell', arguments: 'ls' },
573
+ { type: 'tool_result', callId: 'call-v2', toolName: 'shell', status: 'completed', output: 'file.txt' },
574
+ { type: 'assistant_text', text: 'here is the file' },
575
+ ],
576
+ },
577
+ snapshot: {
578
+ history: [
579
+ { role: 'user', type: 'message', content: 'run tool' },
580
+ { role: 'assistant', type: 'message', content: 'here is the file' },
581
+ ],
582
+ previousResponseId: 'resp-v2',
583
+ toolLedger: [
584
+ {
585
+ turnId: 'turn-1',
586
+ callId: 'call-v2',
587
+ toolName: 'shell',
588
+ status: 'completed',
589
+ startedAt: '2026-05-26T00:00:00.000Z',
590
+ completedAt: '2026-05-26T00:00:01.000Z',
591
+ arguments: 'ls',
592
+ output: 'file.txt',
593
+ },
594
+ ],
595
+ model: 'gpt-4o',
596
+ provider: 'openai',
597
+ },
598
+ });
599
+ void writer.close();
600
+ const restored = persistenceModule.loadConversation(id);
601
+ t.truthy(restored);
602
+ t.is(restored.id, id);
603
+ t.is(restored.previousResponseId, 'resp-v2');
604
+ // Reasoning is reconstructed as a standalone history item (matching the live SDK
605
+ // output), NOT folded into the following tool_call's providerData. Folding caused
606
+ // the reasoning_content to be serialized onto both the assistant message and the
607
+ // tool call (duplicate reasoning_content) by the chat-completions converter.
608
+ t.is(restored.history.length, 5);
609
+ t.deepEqual(restored.history[0], { role: 'user', type: 'message', content: 'run tool' });
610
+ t.deepEqual(restored.history[1], {
611
+ type: 'reasoning',
612
+ content: [{ type: 'reasoning_text', text: 'thinking about ls' }],
613
+ rawContent: [{ type: 'reasoning_text', text: 'thinking about ls' }],
614
+ });
615
+ t.deepEqual(restored.history[2], {
616
+ type: 'function_call',
617
+ callId: 'call-v2',
618
+ name: 'shell',
619
+ arguments: 'ls',
620
+ });
621
+ t.deepEqual(restored.history[3], {
622
+ type: 'function_call_result',
623
+ callId: 'call-v2',
624
+ name: 'shell',
625
+ output: 'file.txt',
626
+ });
627
+ t.deepEqual(restored.history[4], {
628
+ role: 'assistant',
629
+ type: 'message',
630
+ status: 'completed',
631
+ content: [{ type: 'output_text', text: 'here is the file' }],
632
+ });
633
+ t.is(restored.toolLedger.length, 1);
634
+ t.is(restored.toolLedger[0].callId, 'call-v2');
635
+ // exact messages ordering: user, reasoning, command, bot
636
+ t.is(restored.messages.length, 4);
637
+ t.is(restored.messages[0].sender, 'user');
638
+ t.is(restored.messages[1].sender, 'reasoning');
639
+ t.is(restored.messages[1].text, 'thinking about ls');
640
+ t.is(restored.messages[2].sender, 'command');
641
+ t.is(restored.messages[2].status, 'completed');
642
+ t.is(restored.messages[3].sender, 'bot');
643
+ t.is(restored.messages[3].text, 'here is the file');
644
+ });
645
+ test.serial('session logging writes compact v3 assistant_turn state without cumulative snapshot', async (t) => {
646
+ const id = persistenceModule.generateId();
647
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
648
+ writer.init({
649
+ id,
650
+ createdAt: '2026-05-26T00:00:00.000Z',
651
+ projectPath: '/workspace/v3',
652
+ model: 'gpt-5',
653
+ provider: 'openai',
654
+ });
655
+ const stream = new MockStream();
656
+ stream.output = [
657
+ {
658
+ role: 'assistant',
659
+ type: 'message',
660
+ status: 'completed',
661
+ content: [{ type: 'output_text', text: 'Done.' }],
662
+ },
663
+ ];
664
+ const session = new ConversationSession(id, {
665
+ agentClient: {
666
+ startStream: async () => stream,
667
+ getProvider: () => 'openai',
668
+ },
669
+ deps: {
670
+ logger: stubLogger,
671
+ settingsService: createMockSettingsService({
672
+ 'agent.model': 'gpt-5',
673
+ 'agent.provider': 'openai',
674
+ }),
675
+ },
676
+ sessionStartedAt: '2026-05-26T00:00:00.000Z',
677
+ });
678
+ session.setLogSink((event) => writer.append(event));
679
+ const result = await session.sendMessage('hello');
680
+ t.is(result.type, 'response');
681
+ await writer.close();
682
+ const filePath = path.join(testDir, `${id}.jsonl`);
683
+ const envelopes = fs
684
+ .readFileSync(filePath, 'utf-8')
685
+ .trim()
686
+ .split('\n')
687
+ .map((line) => JSON.parse(line));
688
+ const turnEnvelope = envelopes.find((env) => env.event?.type === 'assistant_turn');
689
+ t.truthy(turnEnvelope);
690
+ t.is(turnEnvelope.v, 3);
691
+ t.deepEqual(turnEnvelope.event.state, {
692
+ previousResponseId: 'resp-v3',
693
+ model: 'gpt-5',
694
+ provider: 'openai',
695
+ });
696
+ t.is(turnEnvelope.event.displayUsage, undefined);
697
+ t.is(turnEnvelope.event.snapshot, undefined);
698
+ t.is(turnEnvelope.event.turn.items.length, 1);
699
+ t.is(turnEnvelope.event.turn.items[0].text, 'Done.');
700
+ });
701
+ test.serial('session logging persists displayUsage separately from cumulative assistant_turn usage', async (t) => {
702
+ const id = persistenceModule.generateId();
703
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
704
+ writer.init({
705
+ id,
706
+ createdAt: '2026-05-26T00:00:00.000Z',
707
+ projectPath: '/workspace/display-usage',
708
+ model: 'gpt-5',
709
+ provider: 'openai',
710
+ });
711
+ const initialStream = new MockStream([
712
+ {
713
+ type: 'response.done',
714
+ response: {
715
+ usage: { input_tokens: 100, output_tokens: 10 },
716
+ },
717
+ },
718
+ ]);
719
+ initialStream.state = {
720
+ approve: () => { },
721
+ usage: { inputTokens: 120, outputTokens: 12, totalTokens: 132 },
722
+ };
723
+ initialStream.interruptions = [
724
+ {
725
+ name: 'shell',
726
+ arguments: { command: 'ls' },
727
+ callId: 'call-1',
728
+ agent: { name: 'Agent' },
729
+ },
730
+ ];
731
+ const continuationStream = new MockStream([
732
+ {
733
+ type: 'response.done',
734
+ response: {
735
+ usage: { input_tokens: 175, output_tokens: 18 },
736
+ },
737
+ },
738
+ ]);
739
+ continuationStream.newItems = [
740
+ {
741
+ type: 'function_call',
742
+ callId: 'call-1',
743
+ name: 'shell',
744
+ arguments: { command: 'ls' },
745
+ },
746
+ {
747
+ type: 'function_call_result',
748
+ callId: 'call-1',
749
+ name: 'shell',
750
+ output: 'files',
751
+ },
752
+ {
753
+ role: 'assistant',
754
+ type: 'message',
755
+ content: [{ type: 'output_text', text: 'Done.' }],
756
+ },
757
+ ];
758
+ continuationStream.output = [
759
+ {
760
+ role: 'assistant',
761
+ type: 'message',
762
+ status: 'completed',
763
+ content: [{ type: 'output_text', text: 'Done.' }],
764
+ },
765
+ ];
766
+ continuationStream.state = {
767
+ usage: { inputTokens: 300, outputTokens: 50, totalTokens: 350 },
768
+ };
769
+ const session = new ConversationSession(id, {
770
+ agentClient: {
771
+ getProvider: () => 'openai',
772
+ startStream: async () => initialStream,
773
+ continueRunStream: async () => continuationStream,
774
+ },
775
+ deps: {
776
+ logger: stubLogger,
777
+ settingsService: createMockSettingsService({
778
+ 'agent.model': 'gpt-5',
779
+ 'agent.provider': 'openai',
780
+ }),
781
+ },
782
+ sessionStartedAt: '2026-05-26T00:00:00.000Z',
783
+ });
784
+ session.shellAutoApproval = {
785
+ resolveAdvisoryForInterruption: async () => ({ model: 'gpt-5', reasoning: 'allow', decision: 'approve' }),
786
+ shouldAutoApprove: () => true,
787
+ clearCache: () => { },
788
+ };
789
+ session.setLogSink((event) => writer.append(event));
790
+ await session.sendMessage('hello');
791
+ await writer.close();
792
+ const filePath = path.join(testDir, `${id}.jsonl`);
793
+ const envelopes = fs
794
+ .readFileSync(filePath, 'utf-8')
795
+ .trim()
796
+ .split('\n')
797
+ .map((line) => JSON.parse(line));
798
+ const turnEnvelope = envelopes.find((env) => env.event?.type === 'assistant_turn');
799
+ t.truthy(turnEnvelope);
800
+ t.deepEqual(turnEnvelope.event.usage, { prompt_tokens: 300, completion_tokens: 50, total_tokens: 350 });
801
+ t.deepEqual(turnEnvelope.event.displayUsage, { prompt_tokens: 175, completion_tokens: 18, total_tokens: 193 });
802
+ });
803
+ test.serial('replay: interrupted v2 logs without assistant_turn still recover from coarse events', (t) => {
804
+ const id = persistenceModule.generateId();
805
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
806
+ writer.init({ id, createdAt: '2026-05-26T00:00:00.000Z' });
807
+ writer.append({ type: 'user_message', message: { id: 'u1', sender: 'user', text: 'interrupted' } });
808
+ writer.append({ type: 'tool_started', toolCallId: 'call-coarse', toolName: 'shell', arguments: 'ls' });
809
+ writer.append({
810
+ type: 'command_message',
811
+ message: {
812
+ id: 'cmd-coarse',
813
+ sender: 'command',
814
+ status: 'completed',
815
+ command: 'ls',
816
+ output: 'some files',
817
+ success: true,
818
+ callId: 'call-coarse',
819
+ toolName: 'shell',
820
+ },
821
+ });
822
+ // No assistant_turn written!
823
+ void writer.close();
824
+ const restored = persistenceModule.loadConversation(id);
825
+ t.truthy(restored);
826
+ t.true(restored.replayWarnings.some((w) => w.includes('interrupted')));
827
+ t.is(restored.messages.length, 3); // user, command, system interrupted warning
828
+ t.is(restored.messages[0].sender, 'user');
829
+ t.is(restored.messages[1].sender, 'command');
830
+ t.is(restored.messages[1].status, 'completed');
831
+ t.is(restored.messages[2].sender, 'system');
832
+ });
833
+ test.serial('session logging with auto-approved tool continuation writes only one assistant_turn containing all details', async (t) => {
834
+ const id = persistenceModule.generateId();
835
+ const writer = createConversationLogWriter({ sessionId: id, dir: testDir, logger: stubLogger });
836
+ writer.init({
837
+ id,
838
+ createdAt: '2026-05-26T00:00:00.000Z',
839
+ projectPath: '/workspace/autoapprove',
840
+ model: 'gpt-5',
841
+ provider: 'openai',
842
+ });
843
+ const stream1 = new MockStream();
844
+ stream1.state = {
845
+ approve: () => { },
846
+ };
847
+ stream1.interruptions = [
848
+ {
849
+ name: 'shell',
850
+ arguments: { command: 'echo hello' },
851
+ callId: 'call-1',
852
+ agent: { name: 'Agent' },
853
+ },
854
+ ];
855
+ let continueCalled = false;
856
+ const mockAgentClient = {
857
+ getProvider: () => 'openai',
858
+ startStream: async () => stream1,
859
+ continueRunStream: async () => {
860
+ continueCalled = true;
861
+ const stream2 = new MockStream();
862
+ stream2.newItems = [
863
+ {
864
+ type: 'function_call',
865
+ callId: 'call-1',
866
+ name: 'shell',
867
+ arguments: { command: 'echo hello' },
868
+ },
869
+ {
870
+ type: 'function_call_result',
871
+ callId: 'call-1',
872
+ name: 'shell',
873
+ output: 'hello\n',
874
+ },
875
+ {
876
+ role: 'assistant',
877
+ type: 'message',
878
+ content: [{ type: 'output_text', text: 'Done continuation.' }],
879
+ },
880
+ ];
881
+ stream2.output = [
882
+ {
883
+ role: 'assistant',
884
+ type: 'message',
885
+ status: 'completed',
886
+ content: [{ type: 'output_text', text: 'Done continuation.' }],
887
+ },
888
+ ];
889
+ return stream2;
890
+ },
891
+ };
892
+ const session = new ConversationSession(id, {
893
+ agentClient: mockAgentClient,
894
+ deps: {
895
+ logger: stubLogger,
896
+ settingsService: createMockSettingsService({
897
+ 'agent.model': 'gpt-5',
898
+ 'agent.provider': 'openai',
899
+ }),
900
+ },
901
+ sessionStartedAt: '2026-05-26T00:00:00.000Z',
902
+ });
903
+ // Inject a mock shellAutoApproval resolver that auto-approves
904
+ session.shellAutoApproval = {
905
+ resolveAdvisoryForInterruption: async () => ({ model: 'gpt-5', reasoning: 'allow', decision: 'approve' }),
906
+ shouldAutoApprove: () => true,
907
+ clearCache: () => { },
908
+ };
909
+ session.setLogSink((event) => writer.append(event));
910
+ const result = await session.sendMessage('hello');
911
+ t.is(result.type, 'response');
912
+ t.true(continueCalled);
913
+ await writer.close();
914
+ // Read raw file contents to check what was written to disk
915
+ const filePath = path.join(testDir, `${id}.jsonl`);
916
+ const envelopes = fs
917
+ .readFileSync(filePath, 'utf-8')
918
+ .trim()
919
+ .split('\n')
920
+ .map((line) => JSON.parse(line));
921
+ const assistantTurnEnvelopes = envelopes.filter((env) => env.event?.type === 'assistant_turn');
922
+ // Verify that EXACTLY one assistant_turn event was logged!
923
+ t.is(assistantTurnEnvelopes.length, 1);
924
+ const turnEvent = assistantTurnEnvelopes[0].event;
925
+ t.truthy(turnEvent);
926
+ t.is(turnEvent.turn.items.length, 3); // tool_call, tool_result, assistant_text
927
+ t.is(turnEvent.turn.items[0].type, 'tool_call');
928
+ t.is(turnEvent.turn.items[0].callId, 'call-1');
929
+ t.is(turnEvent.turn.items[1].type, 'tool_result');
930
+ t.is(turnEvent.turn.items[1].callId, 'call-1');
931
+ t.is(turnEvent.turn.items[2].type, 'assistant_text');
932
+ t.is(turnEvent.turn.items[2].text, 'Done continuation.');
933
+ });
934
+ test.serial('ensureConversationsDir: automatically migrates files from log to data directory', (t) => {
935
+ const logDir = fs.mkdtempSync(path.join(os.tmpdir(), 'term2-test-log-'));
936
+ const dbDir = fs.mkdtempSync(path.join(os.tmpdir(), 'term2-test-db-'));
937
+ try {
938
+ // Create dummy files in the old log directory
939
+ const testFile1 = 'test-session-1.jsonl';
940
+ const testFile2 = 'last.json';
941
+ const otherFile = 'unrelated.txt';
942
+ fs.writeFileSync(path.join(logDir, testFile1), 'envelope 1', 'utf-8');
943
+ fs.writeFileSync(path.join(logDir, testFile2), 'last data', 'utf-8');
944
+ fs.writeFileSync(path.join(logDir, otherFile), 'text', 'utf-8');
945
+ // Setup environments and trigger ensureConversationsDir by setting test override
946
+ process.env['TERM2_TEST_LOG_DIR'] = logDir;
947
+ process.env['TERM2_TEST_DB_DIR'] = dbDir;
948
+ persistenceModule.setConversationsDirForTest(dbDir);
949
+ // Call loadConversation for a non-existent ID to trigger ensureConversationsDir
950
+ persistenceModule.loadConversation('non-existent-id');
951
+ // Verify files are migrated
952
+ t.true(fs.existsSync(path.join(dbDir, testFile1)));
953
+ t.true(fs.existsSync(path.join(dbDir, testFile2)));
954
+ t.false(fs.existsSync(path.join(dbDir, otherFile))); // Unrelated files should not be migrated
955
+ t.false(fs.existsSync(path.join(logDir, testFile1)));
956
+ t.false(fs.existsSync(path.join(logDir, testFile2)));
957
+ t.is(fs.readFileSync(path.join(dbDir, testFile1), 'utf-8'), 'envelope 1');
958
+ t.is(fs.readFileSync(path.join(dbDir, testFile2), 'utf-8'), 'last data');
959
+ }
960
+ finally {
961
+ // Cleanup
962
+ delete process.env['TERM2_TEST_LOG_DIR'];
963
+ delete process.env['TERM2_TEST_DB_DIR'];
964
+ fs.rmSync(logDir, { recursive: true, force: true });
965
+ fs.rmSync(dbDir, { recursive: true, force: true });
966
+ }
967
+ });
968
+ test.serial('ensureConversationsDir: migrated conversations are not resurrected after deletion', (t) => {
969
+ const logDir = fs.mkdtempSync(path.join(os.tmpdir(), 'term2-test-log-'));
970
+ const dbDir = fs.mkdtempSync(path.join(os.tmpdir(), 'term2-test-db-'));
971
+ try {
972
+ const migratedId = 'migrated-session';
973
+ fs.writeFileSync(path.join(logDir, `${migratedId}.jsonl`), 'stub', 'utf-8');
974
+ process.env['TERM2_TEST_LOG_DIR'] = logDir;
975
+ process.env['TERM2_TEST_DB_DIR'] = dbDir;
976
+ persistenceModule.setConversationsDirForTest(dbDir);
977
+ persistenceModule.loadConversation('non-existent-id');
978
+ t.true(fs.existsSync(path.join(dbDir, `${migratedId}.jsonl`)));
979
+ t.true(persistenceModule.deleteConversation(migratedId));
980
+ t.false(fs.existsSync(path.join(dbDir, `${migratedId}.jsonl`)));
981
+ persistenceModule.loadConversation('another-non-existent-id');
982
+ t.false(fs.existsSync(path.join(dbDir, `${migratedId}.jsonl`)));
983
+ t.false(fs.existsSync(path.join(logDir, `${migratedId}.jsonl`)));
984
+ }
985
+ finally {
986
+ delete process.env['TERM2_TEST_LOG_DIR'];
987
+ delete process.env['TERM2_TEST_DB_DIR'];
988
+ fs.rmSync(logDir, { recursive: true, force: true });
989
+ fs.rmSync(dbDir, { recursive: true, force: true });
990
+ }
991
+ });
548
992
  //# sourceMappingURL=conversation-persistence.test.js.map