@qduc/term2 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (488) hide show
  1. package/dist/agent.d.ts +1 -1
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +55 -7
  4. package/dist/agent.js.map +1 -1
  5. package/dist/agent.test.js +135 -10
  6. package/dist/agent.test.js.map +1 -1
  7. package/dist/app.d.ts +15 -0
  8. package/dist/app.d.ts.map +1 -1
  9. package/dist/app.js +86 -16
  10. package/dist/app.js.map +1 -1
  11. package/dist/app.startup-banner.test.js +48 -1
  12. package/dist/app.startup-banner.test.js.map +1 -1
  13. package/dist/cli.js +226 -28
  14. package/dist/cli.js.map +1 -1
  15. package/dist/components/ApprovalPrompt.d.ts.map +1 -1
  16. package/dist/components/ApprovalPrompt.js +11 -35
  17. package/dist/components/ApprovalPrompt.js.map +1 -1
  18. package/dist/components/Banner.d.ts.map +1 -1
  19. package/dist/components/Banner.js +2 -7
  20. package/dist/components/Banner.js.map +1 -1
  21. package/dist/components/BottomArea.d.ts +7 -1
  22. package/dist/components/BottomArea.d.ts.map +1 -1
  23. package/dist/components/BottomArea.js +2 -2
  24. package/dist/components/BottomArea.js.map +1 -1
  25. package/dist/components/CommandMessage.d.ts.map +1 -1
  26. package/dist/components/CommandMessage.js +38 -42
  27. package/dist/components/CommandMessage.js.map +1 -1
  28. package/dist/components/CommandMessage.test.js +69 -0
  29. package/dist/components/CommandMessage.test.js.map +1 -1
  30. package/dist/components/DiffView.d.ts +7 -0
  31. package/dist/components/DiffView.d.ts.map +1 -0
  32. package/dist/components/DiffView.js +80 -0
  33. package/dist/components/DiffView.js.map +1 -0
  34. package/dist/components/DiffView.test.d.ts +2 -0
  35. package/dist/components/DiffView.test.d.ts.map +1 -0
  36. package/dist/components/DiffView.test.js +45 -0
  37. package/dist/components/DiffView.test.js.map +1 -0
  38. package/dist/components/Input/PopupManager.d.ts +7 -0
  39. package/dist/components/Input/PopupManager.d.ts.map +1 -1
  40. package/dist/components/Input/PopupManager.js +4 -2
  41. package/dist/components/Input/PopupManager.js.map +1 -1
  42. package/dist/components/Input/determine-active-menu.d.ts.map +1 -1
  43. package/dist/components/Input/determine-active-menu.js +4 -1
  44. package/dist/components/Input/determine-active-menu.js.map +1 -1
  45. package/dist/components/Input/popup-props.d.ts +3 -1
  46. package/dist/components/Input/popup-props.d.ts.map +1 -1
  47. package/dist/components/Input/popup-props.js +7 -1
  48. package/dist/components/Input/popup-props.js.map +1 -1
  49. package/dist/components/InputBox.d.ts +6 -0
  50. package/dist/components/InputBox.d.ts.map +1 -1
  51. package/dist/components/InputBox.js +74 -11
  52. package/dist/components/InputBox.js.map +1 -1
  53. package/dist/components/InputBox.menu-logic.test.js +16 -1
  54. package/dist/components/InputBox.menu-logic.test.js.map +1 -1
  55. package/dist/components/InputBox.test.js +1 -0
  56. package/dist/components/InputBox.test.js.map +1 -1
  57. package/dist/components/MarkdownRenderer.d.ts.map +1 -1
  58. package/dist/components/MarkdownRenderer.js +52 -1
  59. package/dist/components/MarkdownRenderer.js.map +1 -1
  60. package/dist/components/MessageList.d.ts.map +1 -1
  61. package/dist/components/MessageList.js +3 -2
  62. package/dist/components/MessageList.js.map +1 -1
  63. package/dist/components/MessageList.test.js +16 -0
  64. package/dist/components/MessageList.test.js.map +1 -1
  65. package/dist/components/SettingsValueSelectionMenu.d.ts.map +1 -1
  66. package/dist/components/SettingsValueSelectionMenu.js +4 -1
  67. package/dist/components/SettingsValueSelectionMenu.js.map +1 -1
  68. package/dist/components/StatusBar.d.ts.map +1 -1
  69. package/dist/components/StatusBar.js +5 -3
  70. package/dist/components/StatusBar.js.map +1 -1
  71. package/dist/components/StatusBar.test.js +24 -0
  72. package/dist/components/StatusBar.test.js.map +1 -1
  73. package/dist/components/SubagentActivityMessage.d.ts.map +1 -1
  74. package/dist/components/SubagentActivityMessage.js +16 -1
  75. package/dist/components/SubagentActivityMessage.js.map +1 -1
  76. package/dist/components/UndoSelectionMenu.d.ts +12 -0
  77. package/dist/components/UndoSelectionMenu.d.ts.map +1 -0
  78. package/dist/components/UndoSelectionMenu.js +46 -0
  79. package/dist/components/UndoSelectionMenu.js.map +1 -0
  80. package/dist/components/UndoSelectionMenu.test.d.ts +2 -0
  81. package/dist/components/UndoSelectionMenu.test.d.ts.map +1 -0
  82. package/dist/components/UndoSelectionMenu.test.js +40 -0
  83. package/dist/components/UndoSelectionMenu.test.js.map +1 -0
  84. package/dist/components/theme.d.ts +1 -1
  85. package/dist/components/theme.d.ts.map +1 -1
  86. package/dist/components/theme.js +1 -3
  87. package/dist/components/theme.js.map +1 -1
  88. package/dist/context/InputContext.d.ts +1 -1
  89. package/dist/context/InputContext.d.ts.map +1 -1
  90. package/dist/context/InputContext.js.map +1 -1
  91. package/dist/hooks/use-app-commands.d.ts +14 -2
  92. package/dist/hooks/use-app-commands.d.ts.map +1 -1
  93. package/dist/hooks/use-app-commands.js +115 -32
  94. package/dist/hooks/use-app-commands.js.map +1 -1
  95. package/dist/hooks/use-app-commands.test.js +231 -1
  96. package/dist/hooks/use-app-commands.test.js.map +1 -1
  97. package/dist/hooks/use-conversation.clear.test.d.ts +2 -0
  98. package/dist/hooks/use-conversation.clear.test.d.ts.map +1 -0
  99. package/dist/hooks/use-conversation.clear.test.js +49 -0
  100. package/dist/hooks/use-conversation.clear.test.js.map +1 -0
  101. package/dist/hooks/use-conversation.d.ts +12 -2
  102. package/dist/hooks/use-conversation.d.ts.map +1 -1
  103. package/dist/hooks/use-conversation.js +88 -5
  104. package/dist/hooks/use-conversation.js.map +1 -1
  105. package/dist/hooks/use-conversation.max-turns-undo.test.d.ts +2 -0
  106. package/dist/hooks/use-conversation.max-turns-undo.test.d.ts.map +1 -0
  107. package/dist/hooks/use-conversation.max-turns-undo.test.js +118 -0
  108. package/dist/hooks/use-conversation.max-turns-undo.test.js.map +1 -0
  109. package/dist/hooks/use-conversation.resume.test.d.ts +2 -0
  110. package/dist/hooks/use-conversation.resume.test.d.ts.map +1 -0
  111. package/dist/hooks/use-conversation.resume.test.js +26 -0
  112. package/dist/hooks/use-conversation.resume.test.js.map +1 -0
  113. package/dist/hooks/use-escape-key.d.ts.map +1 -1
  114. package/dist/hooks/use-escape-key.js +14 -7
  115. package/dist/hooks/use-escape-key.js.map +1 -1
  116. package/dist/hooks/use-escape-key.test.js +168 -4
  117. package/dist/hooks/use-escape-key.test.js.map +1 -1
  118. package/dist/hooks/use-input-history.d.ts +4 -3
  119. package/dist/hooks/use-input-history.d.ts.map +1 -1
  120. package/dist/hooks/use-input-history.js +10 -8
  121. package/dist/hooks/use-input-history.js.map +1 -1
  122. package/dist/hooks/use-input-history.test.d.ts +2 -0
  123. package/dist/hooks/use-input-history.test.d.ts.map +1 -0
  124. package/dist/hooks/use-input-history.test.js +43 -0
  125. package/dist/hooks/use-input-history.test.js.map +1 -0
  126. package/dist/hooks/use-mode-handlers.d.ts +10 -2
  127. package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
  128. package/dist/hooks/use-mode-handlers.js +21 -2
  129. package/dist/hooks/use-mode-handlers.js.map +1 -1
  130. package/dist/hooks/use-model-selection.d.ts +3 -0
  131. package/dist/hooks/use-model-selection.d.ts.map +1 -1
  132. package/dist/hooks/use-model-selection.js +3 -0
  133. package/dist/hooks/use-model-selection.js.map +1 -1
  134. package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
  135. package/dist/hooks/use-runtime-settings.js +15 -6
  136. package/dist/hooks/use-runtime-settings.js.map +1 -1
  137. package/dist/hooks/use-settings-completion.d.ts.map +1 -1
  138. package/dist/hooks/use-settings-completion.js +0 -1
  139. package/dist/hooks/use-settings-completion.js.map +1 -1
  140. package/dist/hooks/use-settings-value-completion.d.ts +1 -0
  141. package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
  142. package/dist/hooks/use-settings-value-completion.js +25 -19
  143. package/dist/hooks/use-settings-value-completion.js.map +1 -1
  144. package/dist/hooks/use-shell-mode.d.ts.map +1 -1
  145. package/dist/hooks/use-shell-mode.js +2 -1
  146. package/dist/hooks/use-shell-mode.js.map +1 -1
  147. package/dist/hooks/use-slash-commands.d.ts +1 -0
  148. package/dist/hooks/use-slash-commands.d.ts.map +1 -1
  149. package/dist/hooks/use-slash-commands.js +10 -1
  150. package/dist/hooks/use-slash-commands.js.map +1 -1
  151. package/dist/hooks/use-undo-selection.d.ts +19 -0
  152. package/dist/hooks/use-undo-selection.d.ts.map +1 -0
  153. package/dist/hooks/use-undo-selection.js +76 -0
  154. package/dist/hooks/use-undo-selection.js.map +1 -0
  155. package/dist/lib/openai-agent-client.chat.test.js +40 -0
  156. package/dist/lib/openai-agent-client.chat.test.js.map +1 -1
  157. package/dist/lib/openai-agent-client.d.ts +10 -1
  158. package/dist/lib/openai-agent-client.d.ts.map +1 -1
  159. package/dist/lib/openai-agent-client.js +114 -6
  160. package/dist/lib/openai-agent-client.js.map +1 -1
  161. package/dist/lib/openai-agent-client.test.js +23 -0
  162. package/dist/lib/openai-agent-client.test.js.map +1 -1
  163. package/dist/lib/retry-executor.js +45 -7
  164. package/dist/lib/retry-executor.js.map +1 -1
  165. package/dist/lib/retry-executor.test.js +63 -0
  166. package/dist/lib/retry-executor.test.js.map +1 -1
  167. package/dist/non-interactive.d.ts +7 -1
  168. package/dist/non-interactive.d.ts.map +1 -1
  169. package/dist/non-interactive.js +73 -5
  170. package/dist/non-interactive.js.map +1 -1
  171. package/dist/non-interactive.test.js +184 -2
  172. package/dist/non-interactive.test.js.map +1 -1
  173. package/dist/prompts/orchestrator.md +46 -0
  174. package/dist/prompts/plan-mode-info.md +17 -0
  175. package/dist/prompts/prompt-selector.d.ts +2 -1
  176. package/dist/prompts/prompt-selector.d.ts.map +1 -1
  177. package/dist/prompts/prompt-selector.js +5 -1
  178. package/dist/prompts/prompt-selector.js.map +1 -1
  179. package/dist/prompts/prompt-selector.test.js +7 -0
  180. package/dist/prompts/prompt-selector.test.js.map +1 -1
  181. package/dist/prompts/prompt-selector.test.ts +18 -0
  182. package/dist/prompts/prompt-selector.ts +7 -1
  183. package/dist/prompts/reasoning-efficiency.d.ts +7 -0
  184. package/dist/prompts/reasoning-efficiency.d.ts.map +1 -0
  185. package/dist/prompts/reasoning-efficiency.js +19 -0
  186. package/dist/prompts/reasoning-efficiency.js.map +1 -0
  187. package/dist/prompts/reasoning-efficiency.ts +18 -0
  188. package/dist/prompts/search-via-shell.test.js +17 -17
  189. package/dist/prompts/search-via-shell.test.js.map +1 -1
  190. package/dist/prompts/search-via-shell.test.ts +17 -17
  191. package/dist/prompts/shell-auto-approval.d.ts +3 -0
  192. package/dist/prompts/shell-auto-approval.d.ts.map +1 -0
  193. package/dist/prompts/shell-auto-approval.js +23 -0
  194. package/dist/prompts/shell-auto-approval.js.map +1 -0
  195. package/dist/prompts/shell-auto-approval.ts +23 -0
  196. package/dist/prompts/subagent-delegation.d.ts +10 -0
  197. package/dist/prompts/subagent-delegation.d.ts.map +1 -0
  198. package/dist/prompts/subagent-delegation.js +33 -0
  199. package/dist/prompts/subagent-delegation.js.map +1 -0
  200. package/dist/prompts/subagent-delegation.test.d.ts +2 -0
  201. package/dist/prompts/subagent-delegation.test.d.ts.map +1 -0
  202. package/dist/prompts/subagent-delegation.test.js +17 -0
  203. package/dist/prompts/subagent-delegation.test.js.map +1 -0
  204. package/dist/prompts/subagent-delegation.test.ts +21 -0
  205. package/dist/prompts/subagent-delegation.ts +40 -0
  206. package/dist/prompts/subagents/explorer.md +1 -2
  207. package/dist/prompts/subagents/mentor.md +0 -1
  208. package/dist/prompts/subagents/researcher.md +0 -1
  209. package/dist/prompts/subagents/worker.md +3 -4
  210. package/dist/providers/ai-sdk-anthropic.provider.d.ts +1 -0
  211. package/dist/providers/ai-sdk-anthropic.provider.d.ts.map +1 -1
  212. package/dist/providers/ai-sdk-anthropic.provider.js +67 -1
  213. package/dist/providers/ai-sdk-anthropic.provider.js.map +1 -1
  214. package/dist/providers/ai-sdk-anthropic.provider.test.d.ts +2 -0
  215. package/dist/providers/ai-sdk-anthropic.provider.test.d.ts.map +1 -0
  216. package/dist/providers/ai-sdk-anthropic.provider.test.js +87 -0
  217. package/dist/providers/ai-sdk-anthropic.provider.test.js.map +1 -0
  218. package/dist/providers/ai-sdk-logging-fetch.d.ts +1 -1
  219. package/dist/providers/ai-sdk-logging-fetch.d.ts.map +1 -1
  220. package/dist/providers/ai-sdk-logging-fetch.js +67 -44
  221. package/dist/providers/ai-sdk-logging-fetch.js.map +1 -1
  222. package/dist/providers/ai-sdk-logging-fetch.test.js +59 -5
  223. package/dist/providers/ai-sdk-logging-fetch.test.js.map +1 -1
  224. package/dist/providers/common/openai-compatible-messages.d.ts.map +1 -1
  225. package/dist/providers/common/openai-compatible-messages.js +10 -4
  226. package/dist/providers/common/openai-compatible-messages.js.map +1 -1
  227. package/dist/providers/common/openai-compatible-messages.test.js +22 -0
  228. package/dist/providers/common/openai-compatible-messages.test.js.map +1 -1
  229. package/dist/providers/custom-provider-adapter.test.js +39 -1
  230. package/dist/providers/custom-provider-adapter.test.js.map +1 -1
  231. package/dist/providers/openai-compatible.provider.d.ts +11 -1
  232. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  233. package/dist/providers/openai-compatible.provider.js +122 -8
  234. package/dist/providers/openai-compatible.provider.js.map +1 -1
  235. package/dist/providers/openai-compatible.provider.test.js +61 -0
  236. package/dist/providers/openai-compatible.provider.test.js.map +1 -1
  237. package/dist/providers/openai.provider.js +21 -1
  238. package/dist/providers/openai.provider.js.map +1 -1
  239. package/dist/providers/openrouter.provider.d.ts +2 -0
  240. package/dist/providers/openrouter.provider.d.ts.map +1 -1
  241. package/dist/providers/openrouter.provider.js +20 -2
  242. package/dist/providers/openrouter.provider.js.map +1 -1
  243. package/dist/providers/openrouter.provider.request-preprocessing.test.d.ts +2 -0
  244. package/dist/providers/openrouter.provider.request-preprocessing.test.d.ts.map +1 -0
  245. package/dist/providers/openrouter.provider.request-preprocessing.test.js +48 -0
  246. package/dist/providers/openrouter.provider.request-preprocessing.test.js.map +1 -0
  247. package/dist/providers/registry.test.js +27 -2
  248. package/dist/providers/registry.test.js.map +1 -1
  249. package/dist/scripts/eval-auto-approval/dataset.d.ts +2 -2
  250. package/dist/services/agent-stream.d.ts +1 -0
  251. package/dist/services/agent-stream.d.ts.map +1 -1
  252. package/dist/services/approval-flow-coordinator.d.ts +2 -0
  253. package/dist/services/approval-flow-coordinator.d.ts.map +1 -1
  254. package/dist/services/approval-flow-coordinator.js +45 -18
  255. package/dist/services/approval-flow-coordinator.js.map +1 -1
  256. package/dist/services/approval-flow-coordinator.test.js +87 -0
  257. package/dist/services/approval-flow-coordinator.test.js.map +1 -1
  258. package/dist/services/approval-state.d.ts +4 -0
  259. package/dist/services/approval-state.d.ts.map +1 -1
  260. package/dist/services/approval-state.js +2 -0
  261. package/dist/services/approval-state.js.map +1 -1
  262. package/dist/services/approval-state.test.js +21 -0
  263. package/dist/services/approval-state.test.js.map +1 -1
  264. package/dist/services/conversation-clear-save.test.d.ts +2 -0
  265. package/dist/services/conversation-clear-save.test.d.ts.map +1 -0
  266. package/dist/services/conversation-clear-save.test.js +85 -0
  267. package/dist/services/conversation-clear-save.test.js.map +1 -0
  268. package/dist/services/conversation-history-repair.d.ts +25 -0
  269. package/dist/services/conversation-history-repair.d.ts.map +1 -0
  270. package/dist/services/conversation-history-repair.js +227 -0
  271. package/dist/services/conversation-history-repair.js.map +1 -0
  272. package/dist/services/conversation-history-repair.test.d.ts +2 -0
  273. package/dist/services/conversation-history-repair.test.d.ts.map +1 -0
  274. package/dist/services/conversation-history-repair.test.js +76 -0
  275. package/dist/services/conversation-history-repair.test.js.map +1 -0
  276. package/dist/services/conversation-persistence.d.ts +55 -0
  277. package/dist/services/conversation-persistence.d.ts.map +1 -0
  278. package/dist/services/conversation-persistence.js +216 -0
  279. package/dist/services/conversation-persistence.js.map +1 -0
  280. package/dist/services/conversation-persistence.test.d.ts +2 -0
  281. package/dist/services/conversation-persistence.test.d.ts.map +1 -0
  282. package/dist/services/conversation-persistence.test.js +713 -0
  283. package/dist/services/conversation-persistence.test.js.map +1 -0
  284. package/dist/services/conversation-result-builder.d.ts.map +1 -1
  285. package/dist/services/conversation-result-builder.js +13 -0
  286. package/dist/services/conversation-result-builder.js.map +1 -1
  287. package/dist/services/conversation-retry-policy.d.ts +7 -0
  288. package/dist/services/conversation-retry-policy.d.ts.map +1 -1
  289. package/dist/services/conversation-retry-policy.js +34 -0
  290. package/dist/services/conversation-retry-policy.js.map +1 -1
  291. package/dist/services/conversation-retry-policy.test.js +44 -2
  292. package/dist/services/conversation-retry-policy.test.js.map +1 -1
  293. package/dist/services/conversation-service.d.ts +25 -2
  294. package/dist/services/conversation-service.d.ts.map +1 -1
  295. package/dist/services/conversation-service.js +29 -3
  296. package/dist/services/conversation-service.js.map +1 -1
  297. package/dist/services/conversation-session.auto-approval.test.js +7 -10
  298. package/dist/services/conversation-session.auto-approval.test.js.map +1 -1
  299. package/dist/services/conversation-session.d.ts +28 -1
  300. package/dist/services/conversation-session.d.ts.map +1 -1
  301. package/dist/services/conversation-session.js +300 -95
  302. package/dist/services/conversation-session.js.map +1 -1
  303. package/dist/services/conversation-store.d.ts +42 -0
  304. package/dist/services/conversation-store.d.ts.map +1 -1
  305. package/dist/services/conversation-store.js +188 -6
  306. package/dist/services/conversation-store.js.map +1 -1
  307. package/dist/services/conversation-store.test.js +344 -1
  308. package/dist/services/conversation-store.test.js.map +1 -1
  309. package/dist/services/history-service.d.ts +6 -1
  310. package/dist/services/history-service.d.ts.map +1 -1
  311. package/dist/services/history-service.js +46 -6
  312. package/dist/services/history-service.js.map +1 -1
  313. package/dist/services/history-service.test.d.ts +2 -0
  314. package/dist/services/history-service.test.d.ts.map +1 -0
  315. package/dist/services/history-service.test.js +77 -0
  316. package/dist/services/history-service.test.js.map +1 -0
  317. package/dist/services/input-surge-guard.d.ts +37 -0
  318. package/dist/services/input-surge-guard.d.ts.map +1 -0
  319. package/dist/services/input-surge-guard.js +190 -0
  320. package/dist/services/input-surge-guard.js.map +1 -0
  321. package/dist/services/input-surge-guard.test.d.ts +2 -0
  322. package/dist/services/input-surge-guard.test.d.ts.map +1 -0
  323. package/dist/services/input-surge-guard.test.js +126 -0
  324. package/dist/services/input-surge-guard.test.js.map +1 -0
  325. package/dist/services/logging-contract.d.ts +1 -1
  326. package/dist/services/logging-service.d.ts +4 -0
  327. package/dist/services/logging-service.d.ts.map +1 -1
  328. package/dist/services/logging-service.js +91 -33
  329. package/dist/services/logging-service.js.map +1 -1
  330. package/dist/services/mode-notices.d.ts +11 -0
  331. package/dist/services/mode-notices.d.ts.map +1 -0
  332. package/dist/services/mode-notices.js +16 -0
  333. package/dist/services/mode-notices.js.map +1 -0
  334. package/dist/services/model-service.d.ts.map +1 -1
  335. package/dist/services/model-service.js +0 -1
  336. package/dist/services/model-service.js.map +1 -1
  337. package/dist/services/plan-mode-interceptor.d.ts +10 -0
  338. package/dist/services/plan-mode-interceptor.d.ts.map +1 -0
  339. package/dist/services/plan-mode-interceptor.js +38 -0
  340. package/dist/services/plan-mode-interceptor.js.map +1 -0
  341. package/dist/services/plan-mode-interceptor.test.d.ts +2 -0
  342. package/dist/services/plan-mode-interceptor.test.d.ts.map +1 -0
  343. package/dist/services/plan-mode-interceptor.test.js +55 -0
  344. package/dist/services/plan-mode-interceptor.test.js.map +1 -0
  345. package/dist/services/provider-traffic.d.ts +87 -0
  346. package/dist/services/provider-traffic.d.ts.map +1 -0
  347. package/dist/services/provider-traffic.js +545 -0
  348. package/dist/services/provider-traffic.js.map +1 -0
  349. package/dist/services/provider-traffic.test.d.ts +2 -0
  350. package/dist/services/provider-traffic.test.d.ts.map +1 -0
  351. package/dist/services/provider-traffic.test.js +383 -0
  352. package/dist/services/provider-traffic.test.js.map +1 -0
  353. package/dist/services/service-interfaces.d.ts +20 -1
  354. package/dist/services/service-interfaces.d.ts.map +1 -1
  355. package/dist/services/settings-persistence.js +1 -1
  356. package/dist/services/settings-schema.d.ts +107 -4
  357. package/dist/services/settings-schema.d.ts.map +1 -1
  358. package/dist/services/settings-schema.js +103 -5
  359. package/dist/services/settings-schema.js.map +1 -1
  360. package/dist/services/settings-schema.test.js +35 -1
  361. package/dist/services/settings-schema.test.js.map +1 -1
  362. package/dist/services/settings-service.d.ts +3 -1
  363. package/dist/services/settings-service.d.ts.map +1 -1
  364. package/dist/services/settings-service.js +40 -4
  365. package/dist/services/settings-service.js.map +1 -1
  366. package/dist/services/settings-sources.d.ts.map +1 -1
  367. package/dist/services/settings-sources.js +2 -1
  368. package/dist/services/settings-sources.js.map +1 -1
  369. package/dist/services/settings-sources.test.js +4 -0
  370. package/dist/services/settings-sources.test.js.map +1 -1
  371. package/dist/services/shell-auto-approval-evaluator.d.ts +3 -2
  372. package/dist/services/shell-auto-approval-evaluator.d.ts.map +1 -1
  373. package/dist/services/shell-auto-approval-evaluator.js +287 -101
  374. package/dist/services/shell-auto-approval-evaluator.js.map +1 -1
  375. package/dist/services/shell-auto-approval-evaluator.test.js +216 -18
  376. package/dist/services/shell-auto-approval-evaluator.test.js.map +1 -1
  377. package/dist/services/shell-auto-approval-resolver.test.js +1 -1
  378. package/dist/services/shell-auto-approval-resolver.test.js.map +1 -1
  379. package/dist/services/stream-event-processor.d.ts.map +1 -1
  380. package/dist/services/stream-event-processor.js +5 -0
  381. package/dist/services/stream-event-processor.js.map +1 -1
  382. package/dist/services/stream-event-processor.test.js +27 -0
  383. package/dist/services/stream-event-processor.test.js.map +1 -1
  384. package/dist/services/subagents/reasoning-inheritance.test.d.ts +2 -0
  385. package/dist/services/subagents/reasoning-inheritance.test.d.ts.map +1 -0
  386. package/dist/services/subagents/reasoning-inheritance.test.js +73 -0
  387. package/dist/services/subagents/reasoning-inheritance.test.js.map +1 -0
  388. package/dist/services/subagents/subagent-manager.d.ts +2 -0
  389. package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
  390. package/dist/services/subagents/subagent-manager.js +204 -33
  391. package/dist/services/subagents/subagent-manager.js.map +1 -1
  392. package/dist/services/subagents/subagent-manager.test.js +331 -42
  393. package/dist/services/subagents/subagent-manager.test.js.map +1 -1
  394. package/dist/services/subagents/types.d.ts +6 -0
  395. package/dist/services/subagents/types.d.ts.map +1 -1
  396. package/dist/services/terminal-result-collector.d.ts.map +1 -1
  397. package/dist/services/terminal-result-collector.js +1 -2
  398. package/dist/services/terminal-result-collector.js.map +1 -1
  399. package/dist/slash-commands.d.ts +1 -0
  400. package/dist/slash-commands.d.ts.map +1 -1
  401. package/dist/slash-commands.js +8 -1
  402. package/dist/slash-commands.js.map +1 -1
  403. package/dist/slash-commands.test.d.ts +2 -0
  404. package/dist/slash-commands.test.d.ts.map +1 -0
  405. package/dist/slash-commands.test.js +22 -0
  406. package/dist/slash-commands.test.js.map +1 -0
  407. package/dist/tools/apply-patch.d.ts.map +1 -1
  408. package/dist/tools/apply-patch.js +2 -6
  409. package/dist/tools/apply-patch.js.map +1 -1
  410. package/dist/tools/apply-patch.test.js +2 -13
  411. package/dist/tools/apply-patch.test.js.map +1 -1
  412. package/dist/tools/code-context.test.js +4 -3
  413. package/dist/tools/code-context.test.js.map +1 -1
  414. package/dist/tools/create-file.d.ts.map +1 -1
  415. package/dist/tools/create-file.js +2 -3
  416. package/dist/tools/create-file.js.map +1 -1
  417. package/dist/tools/create-file.test.js +3 -14
  418. package/dist/tools/create-file.test.js.map +1 -1
  419. package/dist/tools/edit-healing.d.ts +3 -3
  420. package/dist/tools/edit-healing.d.ts.map +1 -1
  421. package/dist/tools/edit-healing.js.map +1 -1
  422. package/dist/tools/edit-healing.test.js +0 -1
  423. package/dist/tools/edit-healing.test.js.map +1 -1
  424. package/dist/tools/find-files.test.js +3 -2
  425. package/dist/tools/find-files.test.js.map +1 -1
  426. package/dist/tools/grep.d.ts +1 -0
  427. package/dist/tools/grep.d.ts.map +1 -1
  428. package/dist/tools/grep.js +65 -4
  429. package/dist/tools/grep.js.map +1 -1
  430. package/dist/tools/grep.test.js +39 -0
  431. package/dist/tools/grep.test.js.map +1 -0
  432. package/dist/tools/read-file.d.ts +1 -0
  433. package/dist/tools/read-file.d.ts.map +1 -1
  434. package/dist/tools/read-file.js +7 -5
  435. package/dist/tools/read-file.js.map +1 -1
  436. package/dist/tools/read-file.test.js +3 -2
  437. package/dist/tools/read-file.test.js.map +1 -1
  438. package/dist/tools/run-subagent.d.ts +1 -1
  439. package/dist/tools/run-subagent.d.ts.map +1 -1
  440. package/dist/tools/run-subagent.js +27 -23
  441. package/dist/tools/run-subagent.js.map +1 -1
  442. package/dist/tools/run-subagent.test.js +55 -8
  443. package/dist/tools/run-subagent.test.js.map +1 -1
  444. package/dist/tools/search-replace.d.ts +13 -5
  445. package/dist/tools/search-replace.d.ts.map +1 -1
  446. package/dist/tools/search-replace.js +46 -115
  447. package/dist/tools/search-replace.js.map +1 -1
  448. package/dist/tools/search-replace.test.js +275 -240
  449. package/dist/tools/search-replace.test.js.map +1 -1
  450. package/dist/tools/shell.d.ts +1 -0
  451. package/dist/tools/shell.d.ts.map +1 -1
  452. package/dist/tools/shell.js +14 -6
  453. package/dist/tools/shell.js.map +1 -1
  454. package/dist/tools/shell.test.js +52 -0
  455. package/dist/tools/shell.test.js.map +1 -1
  456. package/dist/tools/tool-parameter-schema.test.js +13 -4
  457. package/dist/tools/tool-parameter-schema.test.js.map +1 -1
  458. package/dist/tools/types.d.ts +1 -1
  459. package/dist/tools/types.d.ts.map +1 -1
  460. package/dist/tools/web-fetch.d.ts.map +1 -1
  461. package/dist/tools/web-fetch.js +29 -4
  462. package/dist/tools/web-fetch.js.map +1 -1
  463. package/dist/tools/web-fetch.test.js +64 -0
  464. package/dist/tools/web-fetch.test.js.map +1 -1
  465. package/dist/utils/command-safety/path-analysis.d.ts.map +1 -1
  466. package/dist/utils/command-safety/path-analysis.js +143 -35
  467. package/dist/utils/command-safety/path-analysis.js.map +1 -1
  468. package/dist/utils/conversation-event-handler.d.ts.map +1 -1
  469. package/dist/utils/conversation-event-handler.js +23 -3
  470. package/dist/utils/conversation-event-handler.js.map +1 -1
  471. package/dist/utils/conversation-event-handler.test.js +94 -0
  472. package/dist/utils/conversation-event-handler.test.js.map +1 -1
  473. package/dist/utils/execute-shell.d.ts +2 -0
  474. package/dist/utils/execute-shell.d.ts.map +1 -1
  475. package/dist/utils/execute-shell.js +25 -1
  476. package/dist/utils/execute-shell.js.map +1 -1
  477. package/dist/utils/execute-shell.test.js +22 -0
  478. package/dist/utils/execute-shell.test.js.map +1 -1
  479. package/dist/utils/model-settings.d.ts.map +1 -1
  480. package/dist/utils/model-settings.js +18 -0
  481. package/dist/utils/model-settings.js.map +1 -1
  482. package/dist/utils/output-trim.d.ts.map +1 -1
  483. package/dist/utils/output-trim.js +14 -6
  484. package/dist/utils/output-trim.js.map +1 -1
  485. package/dist/utils/token-usage.test.js +31 -0
  486. package/dist/utils/token-usage.test.js.map +1 -1
  487. package/package.json +3 -3
  488. package/readme.md +271 -44
@@ -0,0 +1,713 @@
1
+ import test from 'ava';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import * as persistenceModule from './conversation-persistence.js';
6
+ let testDir = '';
7
+ // Clean up all conversation files before and after each test
8
+ function cleanupAll() {
9
+ if (testDir && fs.existsSync(testDir)) {
10
+ fs.rmSync(testDir, { recursive: true, force: true });
11
+ }
12
+ }
13
+ function waitForDistinctTimestamp() {
14
+ const start = Date.now();
15
+ while (Date.now() - start < 10) {
16
+ // busy wait for 10ms
17
+ }
18
+ }
19
+ test.beforeEach(() => {
20
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'term2-conversations-test-'));
21
+ persistenceModule.setConversationsDirForTest(testDir);
22
+ });
23
+ test.afterEach.always(cleanupAll);
24
+ test.afterEach.always(() => {
25
+ persistenceModule.setConversationsDirForTest(null);
26
+ testDir = '';
27
+ });
28
+ test.serial('generateId: returns a valid UUID', (t) => {
29
+ const id = persistenceModule.generateId();
30
+ t.regex(id, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
31
+ });
32
+ test.serial('generateId: returns unique IDs', (t) => {
33
+ const id1 = persistenceModule.generateId();
34
+ const id2 = persistenceModule.generateId();
35
+ t.not(id1, id2);
36
+ });
37
+ test.serial('getResumeCommand: returns correct format', (t) => {
38
+ const id = 'test-uuid-123';
39
+ // Local (no SSH)
40
+ t.is(persistenceModule.getResumeCommand(id), 'term2 --resume test-uuid-123');
41
+ // SSH with host
42
+ 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) => {
51
+ const id = persistenceModule.generateId();
52
+ const conversation = {
53
+ id,
54
+ createdAt: new Date().toISOString(),
55
+ updatedAt: new Date().toISOString(),
56
+ 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) => {
78
+ 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) => {
97
+ 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) => {
114
+ 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) => {
140
+ 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) => {
162
+ 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) => {
187
+ 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,
197
+ },
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
+ });
210
+ });
211
+ test.serial('loadConversation: returns null when saved project path differs from expected path', (t) => {
212
+ 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) => {
227
+ 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);
238
+ const result = persistenceModule.loadConversationForProject(id, '/workspace/beta');
239
+ 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
+ });
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');
249
+ });
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) => {
287
+ const id1 = persistenceModule.generateId();
288
+ 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);
307
+ const last = persistenceModule.loadLastConversation();
308
+ t.truthy(last);
309
+ t.is(last.id, id2);
310
+ // Cleanup
311
+ fs.rmSync(persistenceModule.getConversationsDirForTest(), { recursive: true, force: true });
312
+ });
313
+ test.serial('loadLastConversation: surfaces repaired conversation metadata', (t) => {
314
+ 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');
332
+ 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
+ t.is(last, null);
386
+ });
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, []);
416
+ });
417
+ test.serial('loadConversation: old save without orchestratorMode — resolves to false, no error', (t) => {
418
+ 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);
496
+ });
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) => {
548
+ 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
+ }
583
+ });
584
+ test.serial('loadConversationForProject: returns project_mismatch for project path mismatch with sshHost', (t) => {
585
+ 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
+ }
602
+ });
603
+ test.serial('loadConversationForProject: returns loaded when both sshHost and projectPath match', (t) => {
604
+ 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) => {
624
+ 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' }],
655
+ });
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' }],
665
+ });
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' }],
675
+ });
676
+ const last = persistenceModule.loadLastConversation('/workspace/alpha', 'myhost.example.com');
677
+ 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);
693
+ });
694
+ test.serial('loadConversation: unscoped load (no filters) accepts conversation with sshHost', (t) => {
695
+ 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');
712
+ });
713
+ //# sourceMappingURL=conversation-persistence.test.js.map