@qduc/term2 0.7.2 → 0.7.3

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 (469) hide show
  1. package/dist/agent.d.ts +1 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +17 -1
  4. package/dist/agent.js.map +1 -1
  5. package/dist/agent.test.js +36 -0
  6. package/dist/agent.test.js.map +1 -1
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +57 -12
  9. package/dist/app.js.map +1 -1
  10. package/dist/cli.integration.test.js +60 -4
  11. package/dist/cli.integration.test.js.map +1 -1
  12. package/dist/cli.js +33 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/components/ApprovalPrompt.ask-user.test.d.ts +2 -0
  15. package/dist/components/ApprovalPrompt.ask-user.test.d.ts.map +1 -0
  16. package/dist/components/ApprovalPrompt.ask-user.test.js +108 -0
  17. package/dist/components/ApprovalPrompt.ask-user.test.js.map +1 -0
  18. package/dist/components/ApprovalPrompt.d.ts +4 -4
  19. package/dist/components/ApprovalPrompt.d.ts.map +1 -1
  20. package/dist/components/ApprovalPrompt.js +76 -13
  21. package/dist/components/ApprovalPrompt.js.map +1 -1
  22. package/dist/components/BottomArea.d.ts +7 -2
  23. package/dist/components/BottomArea.d.ts.map +1 -1
  24. package/dist/components/BottomArea.js +11 -6
  25. package/dist/components/BottomArea.js.map +1 -1
  26. package/dist/components/BottomArea.test.js +4 -3
  27. package/dist/components/BottomArea.test.js.map +1 -1
  28. package/dist/components/Common/MenuContainer.d.ts +3 -2
  29. package/dist/components/Common/MenuContainer.d.ts.map +1 -1
  30. package/dist/components/Common/MenuContainer.js +14 -2
  31. package/dist/components/Common/MenuContainer.js.map +1 -1
  32. package/dist/components/Common/MenuContainer.test.d.ts +2 -0
  33. package/dist/components/Common/MenuContainer.test.d.ts.map +1 -0
  34. package/dist/components/Common/MenuContainer.test.js +66 -0
  35. package/dist/components/Common/MenuContainer.test.js.map +1 -0
  36. package/dist/components/Input/PopupManager.d.ts +14 -0
  37. package/dist/components/Input/PopupManager.d.ts.map +1 -1
  38. package/dist/components/Input/PopupManager.js +6 -4
  39. package/dist/components/Input/PopupManager.js.map +1 -1
  40. package/dist/components/Input/popup-props.d.ts +3 -1
  41. package/dist/components/Input/popup-props.d.ts.map +1 -1
  42. package/dist/components/Input/popup-props.js +12 -1
  43. package/dist/components/Input/popup-props.js.map +1 -1
  44. package/dist/components/Input/popup-props.test.js +9 -0
  45. package/dist/components/Input/popup-props.test.js.map +1 -1
  46. package/dist/components/InputBox.d.ts +7 -0
  47. package/dist/components/InputBox.d.ts.map +1 -1
  48. package/dist/components/InputBox.js +81 -30
  49. package/dist/components/InputBox.js.map +1 -1
  50. package/dist/components/InputBox.test.js +133 -10
  51. package/dist/components/InputBox.test.js.map +1 -1
  52. package/dist/components/LargeUncachedConfirmationPrompt.d.ts +2 -1
  53. package/dist/components/LargeUncachedConfirmationPrompt.d.ts.map +1 -1
  54. package/dist/components/LargeUncachedConfirmationPrompt.js +8 -3
  55. package/dist/components/LargeUncachedConfirmationPrompt.js.map +1 -1
  56. package/dist/components/LargeUncachedConfirmationPrompt.test.js +5 -4
  57. package/dist/components/LargeUncachedConfirmationPrompt.test.js.map +1 -1
  58. package/dist/components/MessageList.d.ts +2 -1
  59. package/dist/components/MessageList.d.ts.map +1 -1
  60. package/dist/components/MessageList.js +6 -1
  61. package/dist/components/MessageList.js.map +1 -1
  62. package/dist/components/MessageList.test.js +26 -0
  63. package/dist/components/MessageList.test.js.map +1 -1
  64. package/dist/components/PathSelectionMenu.d.ts +1 -0
  65. package/dist/components/PathSelectionMenu.d.ts.map +1 -1
  66. package/dist/components/PathSelectionMenu.js +11 -9
  67. package/dist/components/PathSelectionMenu.js.map +1 -1
  68. package/dist/components/PathSelectionMenu.test.d.ts +2 -0
  69. package/dist/components/PathSelectionMenu.test.d.ts.map +1 -0
  70. package/dist/components/PathSelectionMenu.test.js +17 -0
  71. package/dist/components/PathSelectionMenu.test.js.map +1 -0
  72. package/dist/components/ProviderSelectionMenu.d.ts +15 -0
  73. package/dist/components/ProviderSelectionMenu.d.ts.map +1 -0
  74. package/dist/components/ProviderSelectionMenu.js +172 -0
  75. package/dist/components/ProviderSelectionMenu.js.map +1 -0
  76. package/dist/components/ProviderSelectionMenu.test.d.ts +2 -0
  77. package/dist/components/ProviderSelectionMenu.test.d.ts.map +1 -0
  78. package/dist/components/ProviderSelectionMenu.test.js +97 -0
  79. package/dist/components/ProviderSelectionMenu.test.js.map +1 -0
  80. package/dist/components/SettingsValueSelectionMenu.d.ts +2 -1
  81. package/dist/components/SettingsValueSelectionMenu.d.ts.map +1 -1
  82. package/dist/components/SettingsValueSelectionMenu.js +12 -6
  83. package/dist/components/SettingsValueSelectionMenu.js.map +1 -1
  84. package/dist/components/StatusBar.d.ts +1 -0
  85. package/dist/components/StatusBar.d.ts.map +1 -1
  86. package/dist/components/StatusBar.js +3 -2
  87. package/dist/components/StatusBar.js.map +1 -1
  88. package/dist/components/StatusBar.test.js +11 -0
  89. package/dist/components/StatusBar.test.js.map +1 -1
  90. package/dist/context/InputContext.d.ts +1 -1
  91. package/dist/context/InputContext.d.ts.map +1 -1
  92. package/dist/context/InputContext.js.map +1 -1
  93. package/dist/hooks/use-app-commands.d.ts +2 -1
  94. package/dist/hooks/use-app-commands.d.ts.map +1 -1
  95. package/dist/hooks/use-app-commands.js +11 -2
  96. package/dist/hooks/use-app-commands.js.map +1 -1
  97. package/dist/hooks/use-app-commands.test.js +2 -0
  98. package/dist/hooks/use-app-commands.test.js.map +1 -1
  99. package/dist/hooks/use-conversation.clear.test.js +31 -0
  100. package/dist/hooks/use-conversation.clear.test.js.map +1 -1
  101. package/dist/hooks/use-conversation.d.ts +4 -1
  102. package/dist/hooks/use-conversation.d.ts.map +1 -1
  103. package/dist/hooks/use-conversation.js +18 -2
  104. package/dist/hooks/use-conversation.js.map +1 -1
  105. package/dist/hooks/use-escape-key.d.ts +12 -3
  106. package/dist/hooks/use-escape-key.d.ts.map +1 -1
  107. package/dist/hooks/use-escape-key.js +25 -11
  108. package/dist/hooks/use-escape-key.js.map +1 -1
  109. package/dist/hooks/use-escape-key.test.js +154 -14
  110. package/dist/hooks/use-escape-key.test.js.map +1 -1
  111. package/dist/hooks/use-mode-handlers.d.ts +13 -1
  112. package/dist/hooks/use-mode-handlers.d.ts.map +1 -1
  113. package/dist/hooks/use-mode-handlers.js +22 -1
  114. package/dist/hooks/use-mode-handlers.js.map +1 -1
  115. package/dist/hooks/use-mode-handlers.test.d.ts +2 -0
  116. package/dist/hooks/use-mode-handlers.test.d.ts.map +1 -0
  117. package/dist/hooks/use-mode-handlers.test.js +119 -0
  118. package/dist/hooks/use-mode-handlers.test.js.map +1 -0
  119. package/dist/hooks/use-path-completion.d.ts +10 -3
  120. package/dist/hooks/use-path-completion.d.ts.map +1 -1
  121. package/dist/hooks/use-path-completion.js +53 -9
  122. package/dist/hooks/use-path-completion.js.map +1 -1
  123. package/dist/hooks/use-path-completion.test.js +61 -49
  124. package/dist/hooks/use-path-completion.test.js.map +1 -1
  125. package/dist/hooks/use-provider-selection.d.ts +60 -0
  126. package/dist/hooks/use-provider-selection.d.ts.map +1 -0
  127. package/dist/hooks/use-provider-selection.js +767 -0
  128. package/dist/hooks/use-provider-selection.js.map +1 -0
  129. package/dist/hooks/use-provider-selection.test.d.ts +2 -0
  130. package/dist/hooks/use-provider-selection.test.d.ts.map +1 -0
  131. package/dist/hooks/use-provider-selection.test.js +793 -0
  132. package/dist/hooks/use-provider-selection.test.js.map +1 -0
  133. package/dist/hooks/use-runtime-settings.d.ts.map +1 -1
  134. package/dist/hooks/use-runtime-settings.js +2 -2
  135. package/dist/hooks/use-runtime-settings.js.map +1 -1
  136. package/dist/hooks/use-selection.d.ts +3 -1
  137. package/dist/hooks/use-selection.d.ts.map +1 -1
  138. package/dist/hooks/use-selection.js +115 -24
  139. package/dist/hooks/use-selection.js.map +1 -1
  140. package/dist/hooks/use-selection.test.js +96 -2
  141. package/dist/hooks/use-selection.test.js.map +1 -1
  142. package/dist/hooks/use-settings-value-completion.d.ts +3 -0
  143. package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
  144. package/dist/hooks/use-settings-value-completion.js +140 -18
  145. package/dist/hooks/use-settings-value-completion.js.map +1 -1
  146. package/dist/hooks/use-settings-value-completion.test.js +71 -1
  147. package/dist/hooks/use-settings-value-completion.test.js.map +1 -1
  148. package/dist/hooks/use-trigger-detection.d.ts +4 -2
  149. package/dist/hooks/use-trigger-detection.d.ts.map +1 -1
  150. package/dist/hooks/use-trigger-detection.js +42 -8
  151. package/dist/hooks/use-trigger-detection.js.map +1 -1
  152. package/dist/hooks/use-trigger-detection.test.js +2 -1
  153. package/dist/hooks/use-trigger-detection.test.js.map +1 -1
  154. package/dist/lib/openai-agent-client.chat.test.js +8 -0
  155. package/dist/lib/openai-agent-client.chat.test.js.map +1 -1
  156. package/dist/lib/openai-agent-client.d.ts +11 -6
  157. package/dist/lib/openai-agent-client.d.ts.map +1 -1
  158. package/dist/lib/openai-agent-client.js +199 -15
  159. package/dist/lib/openai-agent-client.js.map +1 -1
  160. package/dist/lib/openai-agent-client.public-methods.test.js +326 -23
  161. package/dist/lib/openai-agent-client.public-methods.test.js.map +1 -1
  162. package/dist/lib/openai-agent-client.test.js +62 -0
  163. package/dist/lib/openai-agent-client.test.js.map +1 -1
  164. package/dist/lib/tool-invoke.d.ts +27 -0
  165. package/dist/lib/tool-invoke.d.ts.map +1 -1
  166. package/dist/lib/tool-invoke.js +124 -95
  167. package/dist/lib/tool-invoke.js.map +1 -1
  168. package/dist/lib/tool-invoke.test.js +84 -22
  169. package/dist/lib/tool-invoke.test.js.map +1 -1
  170. package/dist/non-interactive.d.ts +2 -1
  171. package/dist/non-interactive.d.ts.map +1 -1
  172. package/dist/non-interactive.js +10 -2
  173. package/dist/non-interactive.js.map +1 -1
  174. package/dist/prompts/orchestrator.md +27 -41
  175. package/dist/prompts/plan-mode-info.md +2 -0
  176. package/dist/prompts/prompt-constructor.d.ts.map +1 -1
  177. package/dist/prompts/prompt-constructor.js +8 -0
  178. package/dist/prompts/prompt-constructor.js.map +1 -1
  179. package/dist/prompts/prompt-constructor.test.js +13 -0
  180. package/dist/prompts/prompt-constructor.test.js.map +1 -1
  181. package/dist/prompts/prompt-constructor.test.ts +16 -0
  182. package/dist/prompts/prompt-constructor.ts +10 -0
  183. package/dist/prompts/subagent-delegation.d.ts.map +1 -1
  184. package/dist/prompts/subagent-delegation.js +17 -1
  185. package/dist/prompts/subagent-delegation.js.map +1 -1
  186. package/dist/prompts/subagent-delegation.test.js +21 -0
  187. package/dist/prompts/subagent-delegation.test.js.map +1 -1
  188. package/dist/prompts/subagent-delegation.test.ts +26 -0
  189. package/dist/prompts/subagent-delegation.ts +17 -1
  190. package/dist/prompts/subagents/base-anthropic.md +3 -0
  191. package/dist/prompts/subagents/base-codex.md +3 -0
  192. package/dist/prompts/subagents/base-gpt-5-modern.md +3 -0
  193. package/dist/prompts/subagents/base-simple.md +3 -0
  194. package/dist/prompts/subagents/explorer.md +8 -10
  195. package/dist/prompts/subagents/researcher.md +2 -1
  196. package/dist/prompts/subagents/worker.md +12 -3
  197. package/dist/prompts/subagents/worktree-hygiene.md +6 -0
  198. package/dist/providers/ai-sdk-anthropic.provider.d.ts +4 -1
  199. package/dist/providers/ai-sdk-anthropic.provider.d.ts.map +1 -1
  200. package/dist/providers/ai-sdk-anthropic.provider.js +65 -22
  201. package/dist/providers/ai-sdk-anthropic.provider.js.map +1 -1
  202. package/dist/providers/ai-sdk-anthropic.provider.test.js +24 -1
  203. package/dist/providers/ai-sdk-anthropic.provider.test.js.map +1 -1
  204. package/dist/providers/anthropic-middleware.d.ts +25 -0
  205. package/dist/providers/anthropic-middleware.d.ts.map +1 -0
  206. package/dist/providers/anthropic-middleware.js +32 -0
  207. package/dist/providers/anthropic-middleware.js.map +1 -0
  208. package/dist/providers/anthropic-middleware.test.d.ts +2 -0
  209. package/dist/providers/anthropic-middleware.test.d.ts.map +1 -0
  210. package/dist/providers/anthropic-middleware.test.js +142 -0
  211. package/dist/providers/anthropic-middleware.test.js.map +1 -0
  212. package/dist/providers/codex-responses-model.d.ts.map +1 -1
  213. package/dist/providers/codex-responses-model.js +47 -30
  214. package/dist/providers/codex-responses-model.js.map +1 -1
  215. package/dist/providers/codex-responses-model.test.js +31 -0
  216. package/dist/providers/codex-responses-model.test.js.map +1 -1
  217. package/dist/providers/codex.provider.d.ts.map +1 -1
  218. package/dist/providers/codex.provider.js +16 -9
  219. package/dist/providers/codex.provider.js.map +1 -1
  220. package/dist/providers/codex.provider.test.js +2 -1
  221. package/dist/providers/codex.provider.test.js.map +1 -1
  222. package/dist/providers/custom-provider-adapter.test.js +6 -2
  223. package/dist/providers/custom-provider-adapter.test.js.map +1 -1
  224. package/dist/providers/fallback-responses-model.d.ts +3 -1
  225. package/dist/providers/fallback-responses-model.d.ts.map +1 -1
  226. package/dist/providers/fallback-responses-model.js +22 -3
  227. package/dist/providers/fallback-responses-model.js.map +1 -1
  228. package/dist/providers/fallback-responses-model.test.js +36 -8
  229. package/dist/providers/fallback-responses-model.test.js.map +1 -1
  230. package/dist/providers/fetch/composer.d.ts +1 -0
  231. package/dist/providers/fetch/composer.d.ts.map +1 -1
  232. package/dist/providers/fetch/composer.js +1 -0
  233. package/dist/providers/fetch/composer.js.map +1 -1
  234. package/dist/providers/fetch/composer.test.js +58 -17
  235. package/dist/providers/fetch/composer.test.js.map +1 -1
  236. package/dist/providers/fetch/logging-middleware.d.ts +3 -2
  237. package/dist/providers/fetch/logging-middleware.d.ts.map +1 -1
  238. package/dist/providers/fetch/logging-middleware.js +5 -2
  239. package/dist/providers/fetch/logging-middleware.js.map +1 -1
  240. package/dist/providers/openai-compatible-lazy.d.ts.map +1 -1
  241. package/dist/providers/openai-compatible-lazy.js +2 -1
  242. package/dist/providers/openai-compatible-lazy.js.map +1 -1
  243. package/dist/providers/openai-compatible-middleware.d.ts +5 -2
  244. package/dist/providers/openai-compatible-middleware.d.ts.map +1 -1
  245. package/dist/providers/openai-compatible-middleware.js +8 -11
  246. package/dist/providers/openai-compatible-middleware.js.map +1 -1
  247. package/dist/providers/openai-compatible.provider.d.ts +5 -1
  248. package/dist/providers/openai-compatible.provider.d.ts.map +1 -1
  249. package/dist/providers/openai-compatible.provider.js +59 -37
  250. package/dist/providers/openai-compatible.provider.js.map +1 -1
  251. package/dist/providers/openai-compatible.provider.test.js +67 -3
  252. package/dist/providers/openai-compatible.provider.test.js.map +1 -1
  253. package/dist/providers/openai.provider.d.ts +8 -1
  254. package/dist/providers/openai.provider.d.ts.map +1 -1
  255. package/dist/providers/openai.provider.js +43 -10
  256. package/dist/providers/openai.provider.js.map +1 -1
  257. package/dist/providers/openai.provider.test.js +53 -16
  258. package/dist/providers/openai.provider.test.js.map +1 -1
  259. package/dist/providers/opencode-routing.d.ts +4 -0
  260. package/dist/providers/opencode-routing.d.ts.map +1 -0
  261. package/dist/providers/opencode-routing.js +14 -0
  262. package/dist/providers/opencode-routing.js.map +1 -0
  263. package/dist/providers/opencode-session.d.ts +19 -0
  264. package/dist/providers/opencode-session.d.ts.map +1 -0
  265. package/dist/providers/opencode-session.js +76 -0
  266. package/dist/providers/opencode-session.js.map +1 -0
  267. package/dist/providers/opencode.provider.d.ts +0 -8
  268. package/dist/providers/opencode.provider.d.ts.map +1 -1
  269. package/dist/providers/opencode.provider.js +0 -32
  270. package/dist/providers/opencode.provider.js.map +1 -1
  271. package/dist/providers/opencode.provider.test.d.ts +2 -0
  272. package/dist/providers/opencode.provider.test.d.ts.map +1 -0
  273. package/dist/providers/opencode.provider.test.js +186 -0
  274. package/dist/providers/opencode.provider.test.js.map +1 -0
  275. package/dist/providers/openrouter.provider.createRunner.test.js +5 -0
  276. package/dist/providers/openrouter.provider.createRunner.test.js.map +1 -1
  277. package/dist/providers/openrouter.provider.d.ts.map +1 -1
  278. package/dist/providers/openrouter.provider.js +8 -4
  279. package/dist/providers/openrouter.provider.js.map +1 -1
  280. package/dist/providers/registry.d.ts +3 -0
  281. package/dist/providers/registry.d.ts.map +1 -1
  282. package/dist/providers/registry.js.map +1 -1
  283. package/dist/providers/registry.test.js +1 -0
  284. package/dist/providers/registry.test.js.map +1 -1
  285. package/dist/scripts/eval-auto-approval/runner.js +4 -0
  286. package/dist/scripts/eval-auto-approval/runner.js.map +1 -1
  287. package/dist/services/conversation-integration.test.js +8 -4
  288. package/dist/services/conversation-integration.test.js.map +1 -1
  289. package/dist/services/conversation-persistence.test.js +7 -0
  290. package/dist/services/conversation-persistence.test.js.map +1 -1
  291. package/dist/services/conversation-replay.d.ts.map +1 -1
  292. package/dist/services/conversation-replay.js +95 -4
  293. package/dist/services/conversation-replay.js.map +1 -1
  294. package/dist/services/conversation-replay.test.js +312 -0
  295. package/dist/services/conversation-replay.test.js.map +1 -1
  296. package/dist/services/conversation-result-builder.test.js +9 -0
  297. package/dist/services/conversation-result-builder.test.js.map +1 -1
  298. package/dist/services/conversation-service.d.ts +3 -1
  299. package/dist/services/conversation-service.d.ts.map +1 -1
  300. package/dist/services/conversation-service.js +3 -0
  301. package/dist/services/conversation-service.js.map +1 -1
  302. package/dist/services/conversation-session-reasoning.test.js +5 -1
  303. package/dist/services/conversation-session-reasoning.test.js.map +1 -1
  304. package/dist/services/conversation-session.auto-approval.test.js +25 -1
  305. package/dist/services/conversation-session.auto-approval.test.js.map +1 -1
  306. package/dist/services/conversation-session.d.ts +7 -2
  307. package/dist/services/conversation-session.d.ts.map +1 -1
  308. package/dist/services/conversation-session.js +70 -45
  309. package/dist/services/conversation-session.js.map +1 -1
  310. package/dist/services/file-service.d.ts +20 -4
  311. package/dist/services/file-service.d.ts.map +1 -1
  312. package/dist/services/file-service.js +111 -52
  313. package/dist/services/file-service.js.map +1 -1
  314. package/dist/services/file-service.test.d.ts +2 -0
  315. package/dist/services/file-service.test.d.ts.map +1 -0
  316. package/dist/services/file-service.test.js +60 -0
  317. package/dist/services/file-service.test.js.map +1 -0
  318. package/dist/services/logging-service.d.ts +0 -3
  319. package/dist/services/logging-service.d.ts.map +1 -1
  320. package/dist/services/logging-service.js +12 -8
  321. package/dist/services/logging-service.js.map +1 -1
  322. package/dist/services/mode-notices.d.ts +4 -5
  323. package/dist/services/mode-notices.d.ts.map +1 -1
  324. package/dist/services/mode-notices.js +4 -5
  325. package/dist/services/mode-notices.js.map +1 -1
  326. package/dist/services/notification-service.d.ts +31 -11
  327. package/dist/services/notification-service.d.ts.map +1 -1
  328. package/dist/services/notification-service.js +88 -40
  329. package/dist/services/notification-service.js.map +1 -1
  330. package/dist/services/notification-service.test.js +135 -62
  331. package/dist/services/notification-service.test.js.map +1 -1
  332. package/dist/services/provider-traffic.d.ts +8 -9
  333. package/dist/services/provider-traffic.d.ts.map +1 -1
  334. package/dist/services/provider-traffic.js +24 -12
  335. package/dist/services/provider-traffic.js.map +1 -1
  336. package/dist/services/provider-traffic.test.js +51 -1
  337. package/dist/services/provider-traffic.test.js.map +1 -1
  338. package/dist/services/service-interfaces.d.ts +12 -16
  339. package/dist/services/service-interfaces.d.ts.map +1 -1
  340. package/dist/services/session-context-service.d.ts +8 -0
  341. package/dist/services/session-context-service.d.ts.map +1 -0
  342. package/dist/services/session-context-service.js +19 -0
  343. package/dist/services/session-context-service.js.map +1 -0
  344. package/dist/services/session-context-service.test.d.ts +2 -0
  345. package/dist/services/session-context-service.test.d.ts.map +1 -0
  346. package/dist/services/session-context-service.test.js +38 -0
  347. package/dist/services/session-context-service.test.js.map +1 -0
  348. package/dist/services/settings-env.d.ts.map +1 -1
  349. package/dist/services/settings-env.js +4 -1
  350. package/dist/services/settings-env.js.map +1 -1
  351. package/dist/services/settings-persistence.d.ts.map +1 -1
  352. package/dist/services/settings-persistence.js +0 -1
  353. package/dist/services/settings-persistence.js.map +1 -1
  354. package/dist/services/settings-persistence.test.js +2 -2
  355. package/dist/services/settings-persistence.test.js.map +1 -1
  356. package/dist/services/settings-schema.d.ts +9 -0
  357. package/dist/services/settings-schema.d.ts.map +1 -1
  358. package/dist/services/settings-schema.js +15 -4
  359. package/dist/services/settings-schema.js.map +1 -1
  360. package/dist/services/settings-service.d.ts +8 -0
  361. package/dist/services/settings-service.d.ts.map +1 -1
  362. package/dist/services/settings-service.js +58 -21
  363. package/dist/services/settings-service.js.map +1 -1
  364. package/dist/services/shell-auto-approval-evaluator.d.ts +3 -2
  365. package/dist/services/shell-auto-approval-evaluator.d.ts.map +1 -1
  366. package/dist/services/shell-auto-approval-evaluator.js +3 -3
  367. package/dist/services/shell-auto-approval-evaluator.js.map +1 -1
  368. package/dist/services/shell-auto-approval-evaluator.test.js +20 -0
  369. package/dist/services/shell-auto-approval-evaluator.test.js.map +1 -1
  370. package/dist/services/shell-auto-approval-resolver.d.ts +2 -1
  371. package/dist/services/shell-auto-approval-resolver.d.ts.map +1 -1
  372. package/dist/services/shell-auto-approval-resolver.js +2 -0
  373. package/dist/services/shell-auto-approval-resolver.js.map +1 -1
  374. package/dist/services/shell-auto-approval-resolver.test.js +10 -0
  375. package/dist/services/shell-auto-approval-resolver.test.js.map +1 -1
  376. package/dist/services/subagents/subagent-manager.d.ts +2 -1
  377. package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
  378. package/dist/services/subagents/subagent-manager.js +182 -13
  379. package/dist/services/subagents/subagent-manager.js.map +1 -1
  380. package/dist/services/subagents/subagent-manager.test.js +548 -17
  381. package/dist/services/subagents/subagent-manager.test.js.map +1 -1
  382. package/dist/tools/apply-patch.d.ts.map +1 -1
  383. package/dist/tools/apply-patch.js +2 -2
  384. package/dist/tools/apply-patch.js.map +1 -1
  385. package/dist/tools/ask-user-constants.d.ts +7 -0
  386. package/dist/tools/ask-user-constants.d.ts.map +1 -0
  387. package/dist/tools/ask-user-constants.js +7 -0
  388. package/dist/tools/ask-user-constants.js.map +1 -0
  389. package/dist/tools/ask-user.d.ts +11 -0
  390. package/dist/tools/ask-user.d.ts.map +1 -0
  391. package/dist/tools/ask-user.js +56 -0
  392. package/dist/tools/ask-user.js.map +1 -0
  393. package/dist/tools/ask-user.test.d.ts +2 -0
  394. package/dist/tools/ask-user.test.d.ts.map +1 -0
  395. package/dist/tools/ask-user.test.js +129 -0
  396. package/dist/tools/ask-user.test.js.map +1 -0
  397. package/dist/tools/format-helpers.d.ts +13 -0
  398. package/dist/tools/format-helpers.d.ts.map +1 -1
  399. package/dist/tools/format-helpers.js +34 -0
  400. package/dist/tools/format-helpers.js.map +1 -1
  401. package/dist/tools/format-helpers.test.d.ts +2 -0
  402. package/dist/tools/format-helpers.test.d.ts.map +1 -0
  403. package/dist/tools/format-helpers.test.js +51 -0
  404. package/dist/tools/format-helpers.test.js.map +1 -0
  405. package/dist/tools/search-replace.d.ts.map +1 -1
  406. package/dist/tools/search-replace.js +2 -2
  407. package/dist/tools/search-replace.js.map +1 -1
  408. package/dist/tools/tool-names.d.ts +1 -0
  409. package/dist/tools/tool-names.d.ts.map +1 -1
  410. package/dist/tools/tool-names.js +1 -0
  411. package/dist/tools/tool-names.js.map +1 -1
  412. package/dist/utils/command-safety/_scratch-classify.test.d.ts +2 -0
  413. package/dist/utils/command-safety/_scratch-classify.test.d.ts.map +1 -0
  414. package/dist/utils/command-safety/_scratch-classify.test.js +22 -0
  415. package/dist/utils/command-safety/_scratch-classify.test.js.map +1 -0
  416. package/dist/utils/command-safety/constants.d.ts.map +1 -1
  417. package/dist/utils/command-safety/constants.js +21 -0
  418. package/dist/utils/command-safety/constants.js.map +1 -1
  419. package/dist/utils/command-safety/handlers/git-handler.d.ts.map +1 -1
  420. package/dist/utils/command-safety/handlers/git-handler.js +28 -2
  421. package/dist/utils/command-safety/handlers/git-handler.js.map +1 -1
  422. package/dist/utils/command-safety/path-analysis.d.ts.map +1 -1
  423. package/dist/utils/command-safety/path-analysis.js +6 -0
  424. package/dist/utils/command-safety/path-analysis.js.map +1 -1
  425. package/dist/utils/command-safety/scratch-classify.test.d.ts +2 -0
  426. package/dist/utils/command-safety/scratch-classify.test.d.ts.map +1 -0
  427. package/dist/utils/command-safety/scratch-classify.test.js +22 -0
  428. package/dist/utils/command-safety/scratch-classify.test.js.map +1 -0
  429. package/dist/utils/command-safety/scratch-tally.test.d.ts +2 -0
  430. package/dist/utils/command-safety/scratch-tally.test.d.ts.map +1 -0
  431. package/dist/utils/command-safety/scratch-tally.test.js +68 -0
  432. package/dist/utils/command-safety/scratch-tally.test.js.map +1 -0
  433. package/dist/utils/command-safety.evaluator-false-positives.test.d.ts +2 -0
  434. package/dist/utils/command-safety.evaluator-false-positives.test.d.ts.map +1 -0
  435. package/dist/utils/command-safety.evaluator-false-positives.test.js +72 -0
  436. package/dist/utils/command-safety.evaluator-false-positives.test.js.map +1 -0
  437. package/dist/utils/header-sanitizer.d.ts +2 -0
  438. package/dist/utils/header-sanitizer.d.ts.map +1 -0
  439. package/dist/utils/header-sanitizer.js +38 -0
  440. package/dist/utils/header-sanitizer.js.map +1 -0
  441. package/dist/utils/header-sanitizer.test.d.ts +2 -0
  442. package/dist/utils/header-sanitizer.test.d.ts.map +1 -0
  443. package/dist/utils/header-sanitizer.test.js +56 -0
  444. package/dist/utils/header-sanitizer.test.js.map +1 -0
  445. package/dist/utils/home-directory-start-guard.d.ts +10 -0
  446. package/dist/utils/home-directory-start-guard.d.ts.map +1 -0
  447. package/dist/utils/home-directory-start-guard.js +20 -0
  448. package/dist/utils/home-directory-start-guard.js.map +1 -0
  449. package/dist/utils/home-directory-start-guard.test.d.ts +2 -0
  450. package/dist/utils/home-directory-start-guard.test.d.ts.map +1 -0
  451. package/dist/utils/home-directory-start-guard.test.js +40 -0
  452. package/dist/utils/home-directory-start-guard.test.js.map +1 -0
  453. package/dist/utils/inject-warning-into-tool-output.d.ts +2 -0
  454. package/dist/utils/inject-warning-into-tool-output.d.ts.map +1 -0
  455. package/dist/utils/inject-warning-into-tool-output.js +66 -0
  456. package/dist/utils/inject-warning-into-tool-output.js.map +1 -0
  457. package/dist/utils/provider-traffic-extractor.d.ts +1 -0
  458. package/dist/utils/provider-traffic-extractor.d.ts.map +1 -1
  459. package/dist/utils/provider-traffic-extractor.js +1 -0
  460. package/dist/utils/provider-traffic-extractor.js.map +1 -1
  461. package/dist/utils/provider-traffic-extractor.test.js +2 -0
  462. package/dist/utils/provider-traffic-extractor.test.js.map +1 -1
  463. package/dist/utils/token-usage.d.ts +3 -1
  464. package/dist/utils/token-usage.d.ts.map +1 -1
  465. package/dist/utils/token-usage.js +7 -2
  466. package/dist/utils/token-usage.js.map +1 -1
  467. package/dist/utils/token-usage.test.js +15 -0
  468. package/dist/utils/token-usage.test.js.map +1 -1
  469. package/package.json +1 -1
@@ -0,0 +1,793 @@
1
+ // @ts-ignore
2
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
3
+ import test from 'ava';
4
+ import React, { act, useEffect } from 'react';
5
+ import { render } from 'ink-testing-library';
6
+ import { useProviderSelection } from './use-provider-selection.js';
7
+ import { InputProvider } from '../context/InputContext.js';
8
+ const TestComponent = ({ settingsService, onHookResult, }) => {
9
+ const hook = useProviderSelection(settingsService);
10
+ useEffect(() => {
11
+ onHookResult(hook);
12
+ });
13
+ return null;
14
+ };
15
+ const flush = async () => {
16
+ await act(async () => {
17
+ await new Promise((resolve) => setTimeout(resolve, 10));
18
+ });
19
+ };
20
+ function createMockSettingsService(initialProviders = [], initialActive = 'openai') {
21
+ const settings = new Map([
22
+ ['providers', initialProviders],
23
+ ['agent.provider', initialActive],
24
+ ]);
25
+ return {
26
+ get: (key) => settings.get(key),
27
+ set: (key, value) => settings.set(key, value),
28
+ setPersistent: (key, value) => settings.set(key, value),
29
+ };
30
+ }
31
+ test.serial('useProviderSelection - lists registered and custom providers on open', async (t) => {
32
+ const customProviders = [{ name: 'custom-ollama', type: 'openai-compatible', baseUrl: 'http://localhost:11434/v1' }];
33
+ const settingsService = createMockSettingsService(customProviders, 'openai');
34
+ let hook;
35
+ let renderer;
36
+ await act(async () => {
37
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
38
+ settingsService,
39
+ onHookResult: (h) => {
40
+ hook = h;
41
+ },
42
+ })));
43
+ });
44
+ t.true(hook !== undefined);
45
+ await act(async () => {
46
+ hook.open();
47
+ });
48
+ await flush();
49
+ t.is(hook.phase, 'list');
50
+ const items = hook.items;
51
+ t.true(items.some((item) => item.id === 'openai' && !item.isCustom && item.isActive));
52
+ t.true(items.some((item) => item.id === 'custom-ollama' && item.isCustom && !item.isActive));
53
+ await act(async () => {
54
+ renderer.unmount();
55
+ });
56
+ });
57
+ test.serial('useProviderSelection - selecting a custom provider opens edit_fields directly', async (t) => {
58
+ const customProviders = [{ name: 'custom-ollama', type: 'openai-compatible', baseUrl: 'http://localhost:11434/v1' }];
59
+ const settingsService = createMockSettingsService(customProviders, 'openai');
60
+ let hook;
61
+ let renderer;
62
+ await act(async () => {
63
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
64
+ settingsService,
65
+ onHookResult: (h) => {
66
+ hook = h;
67
+ },
68
+ })));
69
+ });
70
+ await act(async () => {
71
+ hook.open();
72
+ });
73
+ await flush();
74
+ // Test selecting custom-ollama
75
+ await act(async () => {
76
+ const active = hook.getActiveItems();
77
+ const targetIdx = active.findIndex((item) => item.kind === 'provider' && item.id === 'custom-ollama');
78
+ let moves = 0;
79
+ for (let i = 0; i < targetIdx; i++) {
80
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
81
+ moves++;
82
+ }
83
+ }
84
+ for (let i = 0; i < moves; i++) {
85
+ hook.moveDown();
86
+ }
87
+ });
88
+ await flush();
89
+ await act(async () => {
90
+ hook.selectItem();
91
+ });
92
+ await flush();
93
+ t.is(hook.phase, 'edit_fields');
94
+ t.is(hook.selectedIndex, 0);
95
+ t.is(hook.draft?.name, 'custom-ollama');
96
+ t.is(hook.draft?.baseUrl, 'http://localhost:11434/v1');
97
+ await act(async () => {
98
+ renderer.unmount();
99
+ });
100
+ });
101
+ test.serial('useProviderSelection - selecting a built-in provider (except Codex) enters editing flow; Codex is a no-op', async (t) => {
102
+ const settingsService = createMockSettingsService([], 'openai');
103
+ let hook;
104
+ let renderer;
105
+ await act(async () => {
106
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
107
+ settingsService,
108
+ onHookResult: (h) => {
109
+ hook = h;
110
+ },
111
+ })));
112
+ });
113
+ await act(async () => {
114
+ hook.open();
115
+ });
116
+ await flush();
117
+ // Verify Codex is skipped by keyboard navigation
118
+ const active = hook.getActiveItems();
119
+ const codexIdx = active.findIndex((item) => item.kind === 'provider' && item.id === 'codex');
120
+ t.true(codexIdx !== -1);
121
+ // Let's verify that navigating moveDown starting from index 0 skips codexIdx
122
+ t.is(hook.selectedIndex, 0); // Start at OpenAI
123
+ // Move down a fixed number of times. We should never land on codexIdx!
124
+ const visitedIndices = new Set();
125
+ await act(async () => {
126
+ for (let i = 0; i < active.length; i++) {
127
+ visitedIndices.add(hook.selectedIndex);
128
+ hook.moveDown();
129
+ }
130
+ });
131
+ await flush();
132
+ t.false(visitedIndices.has(codexIdx), 'Keyboard navigation should skip Codex');
133
+ // Navigate to OpenAI (first item, idx 0) and select it
134
+ await act(async () => {
135
+ for (let i = 0; i < active.length; i++) {
136
+ if (hook.selectedIndex === 0)
137
+ break;
138
+ hook.moveUp();
139
+ }
140
+ });
141
+ await flush();
142
+ await act(async () => {
143
+ hook.selectItem();
144
+ });
145
+ await flush();
146
+ // OpenAI opens edit_fields
147
+ t.is(hook.phase, 'edit_fields');
148
+ t.is(hook.selectedIndex, 2); // API Key is selected
149
+ t.is(hook.draft?.name, 'OpenAI');
150
+ // Verify we can edit API Key and save it
151
+ await act(async () => {
152
+ hook.selectItem(); // select API Key at index 2
153
+ });
154
+ await flush();
155
+ t.is(hook.phase, 'wizard_key');
156
+ await act(async () => {
157
+ hook.handleTextInputSubmit('sk-mock-openai-key');
158
+ });
159
+ await flush();
160
+ t.is(hook.phase, 'edit_fields');
161
+ t.is(hook.selectedIndex, 2); // Selected row is API Key row
162
+ await act(async () => {
163
+ hook.moveDown(); // Move to "Save Changes" at index 3
164
+ });
165
+ await flush();
166
+ t.is(hook.selectedIndex, 3);
167
+ await act(async () => {
168
+ hook.selectItem(); // Save Changes
169
+ });
170
+ await flush();
171
+ t.is(hook.phase, 'list');
172
+ t.is(settingsService.get('agent.openai.apiKey'), 'sk-mock-openai-key');
173
+ await act(async () => {
174
+ renderer.unmount();
175
+ });
176
+ });
177
+ test.serial('useProviderSelection - requestDelete on a custom provider opens confirm_delete', async (t) => {
178
+ const customProviders = [{ name: 'custom-ollama', type: 'openai-compatible', baseUrl: 'http://localhost:11434/v1' }];
179
+ const settingsService = createMockSettingsService(customProviders, 'openai');
180
+ let hook;
181
+ let renderer;
182
+ await act(async () => {
183
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
184
+ settingsService,
185
+ onHookResult: (h) => {
186
+ hook = h;
187
+ },
188
+ })));
189
+ });
190
+ await act(async () => {
191
+ hook.open();
192
+ });
193
+ await flush();
194
+ // Navigate to custom-ollama
195
+ await act(async () => {
196
+ const active = hook.getActiveItems();
197
+ const targetIdx = active.findIndex((item) => item.kind === 'provider' && item.id === 'custom-ollama');
198
+ let moves = 0;
199
+ for (let i = 0; i < targetIdx; i++) {
200
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
201
+ moves++;
202
+ }
203
+ }
204
+ for (let i = 0; i < moves; i++) {
205
+ hook.moveDown();
206
+ }
207
+ });
208
+ await flush();
209
+ await act(async () => {
210
+ hook.requestDelete();
211
+ });
212
+ await flush();
213
+ t.is(hook.phase, 'confirm_delete');
214
+ // Default cursor should be on "No, keep it" for safety.
215
+ t.is(hook.selectedIndex, 1);
216
+ // Confirming "No, keep it" returns to the list.
217
+ await act(async () => {
218
+ hook.selectItem();
219
+ });
220
+ await flush();
221
+ t.is(hook.phase, 'list');
222
+ t.is(settingsService.get('providers').length, 1);
223
+ // Now request delete again, this time confirm "Yes".
224
+ await act(async () => {
225
+ const active = hook.getActiveItems();
226
+ const targetIdx = active.findIndex((item) => item.kind === 'provider' && item.id === 'custom-ollama');
227
+ let moves = 0;
228
+ for (let i = 0; i < targetIdx; i++) {
229
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
230
+ moves++;
231
+ }
232
+ }
233
+ for (let i = 0; i < moves; i++) {
234
+ hook.moveDown();
235
+ }
236
+ });
237
+ await flush();
238
+ await act(async () => {
239
+ hook.requestDelete();
240
+ });
241
+ await flush();
242
+ t.is(hook.phase, 'confirm_delete');
243
+ await act(async () => {
244
+ hook.moveUp(); // move from default "No" to "Yes"
245
+ });
246
+ await flush();
247
+ t.is(hook.selectedIndex, 0);
248
+ await act(async () => {
249
+ hook.selectItem();
250
+ });
251
+ await flush();
252
+ t.is(hook.phase, 'list');
253
+ t.is(settingsService.get('providers').length, 0);
254
+ t.is(settingsService.get('agent.provider'), 'openai');
255
+ await act(async () => {
256
+ renderer.unmount();
257
+ });
258
+ });
259
+ test.serial('useProviderSelection - requestDelete on a built-in is a no-op', async (t) => {
260
+ const settingsService = createMockSettingsService([], 'openai');
261
+ let hook;
262
+ let renderer;
263
+ await act(async () => {
264
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
265
+ settingsService,
266
+ onHookResult: (h) => {
267
+ hook = h;
268
+ },
269
+ })));
270
+ });
271
+ await act(async () => {
272
+ hook.open();
273
+ });
274
+ await flush();
275
+ // openai is at index 0 in the built-ins.
276
+ await act(async () => {
277
+ hook.requestDelete();
278
+ });
279
+ await flush();
280
+ t.is(hook.phase, 'list');
281
+ await act(async () => {
282
+ renderer.unmount();
283
+ });
284
+ });
285
+ test.serial('useProviderSelection - add provider wizard flow and validation', async (t) => {
286
+ const settingsService = createMockSettingsService([], 'openai');
287
+ let hook;
288
+ let renderer;
289
+ await act(async () => {
290
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
291
+ settingsService,
292
+ onHookResult: (h) => {
293
+ hook = h;
294
+ },
295
+ })));
296
+ });
297
+ await act(async () => {
298
+ hook.open();
299
+ });
300
+ await flush();
301
+ // Select "Add Custom Provider" which is at the end of the list
302
+ await act(async () => {
303
+ const active = hook.getActiveItems();
304
+ const targetIdx = active.findIndex((item) => item.kind === 'add-provider');
305
+ let moves = 0;
306
+ for (let i = 0; i < targetIdx; i++) {
307
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
308
+ moves++;
309
+ }
310
+ }
311
+ for (let i = 0; i < moves; i++) {
312
+ hook.moveDown();
313
+ }
314
+ });
315
+ await flush();
316
+ await act(async () => {
317
+ hook.selectItem();
318
+ });
319
+ await flush();
320
+ t.is(hook.phase, 'wizard_name');
321
+ // Submit empty name (fails)
322
+ let success = false;
323
+ await act(async () => {
324
+ success = hook.handleTextInputSubmit('');
325
+ });
326
+ await flush();
327
+ t.false(success);
328
+ t.is(hook.errorMessage, 'Name cannot be empty.');
329
+ // Submit invalid name format (fails)
330
+ await act(async () => {
331
+ success = hook.handleTextInputSubmit('my provider');
332
+ });
333
+ await flush();
334
+ t.false(success);
335
+ t.is(hook.errorMessage, 'Name must start with a letter or number and contain only letters, numbers, hyphens, underscores, and dots.');
336
+ // Submit duplicate/built-in name (fails)
337
+ await act(async () => {
338
+ success = hook.handleTextInputSubmit('openai');
339
+ });
340
+ await flush();
341
+ t.false(success);
342
+ t.true(hook.errorMessage.includes('already exists'));
343
+ // Submit valid name (success)
344
+ await act(async () => {
345
+ success = hook.handleTextInputSubmit('my-custom-provider');
346
+ });
347
+ await flush();
348
+ t.true(success);
349
+ t.is(hook.phase, 'wizard_type');
350
+ t.is(hook.draft?.name, 'my-custom-provider');
351
+ // Select first type (openai-compatible)
352
+ await act(async () => {
353
+ hook.selectItem();
354
+ });
355
+ await flush();
356
+ t.is(hook.phase, 'wizard_url');
357
+ t.is(hook.draft?.type, 'openai-compatible');
358
+ // Submit invalid URL (fails)
359
+ await act(async () => {
360
+ success = hook.handleTextInputSubmit('not-a-url');
361
+ });
362
+ await flush();
363
+ t.false(success);
364
+ t.is(hook.errorMessage, 'Invalid URL format. Make sure it starts with http:// or https://');
365
+ // Submit valid URL (success)
366
+ await act(async () => {
367
+ success = hook.handleTextInputSubmit('http://localhost:8000/v1');
368
+ });
369
+ await flush();
370
+ t.true(success);
371
+ t.is(hook.phase, 'wizard_key');
372
+ t.is(hook.draft?.baseUrl, 'http://localhost:8000/v1');
373
+ // Submit empty API Key (success)
374
+ await act(async () => {
375
+ success = hook.handleTextInputSubmit('');
376
+ });
377
+ await flush();
378
+ t.is(hook.phase, 'edit_fields');
379
+ const editFields = hook.getActiveItems();
380
+ const baseUrlField = editFields.find((item) => item.kind === 'field' && item.label === 'Base URL');
381
+ t.truthy(baseUrlField);
382
+ t.is(baseUrlField?.detail, 'http://localhost:8000/v1');
383
+ t.false(baseUrlField?.detail?.includes('required') ?? false);
384
+ // Save changes
385
+ t.is(hook.selectedIndex, 4); // "Save Changes" should be pre-selected
386
+ await act(async () => {
387
+ hook.selectItem();
388
+ });
389
+ await flush();
390
+ t.is(hook.phase, 'list');
391
+ t.is(hook.errorMessage, null);
392
+ // Check persisted in settings
393
+ const persisted = settingsService.get('providers');
394
+ t.true(Array.isArray(persisted));
395
+ t.is(persisted.length, 1);
396
+ t.is(persisted[0].name, 'my-custom-provider');
397
+ t.is(persisted[0].type, 'openai-compatible');
398
+ t.is(persisted[0].baseUrl, 'http://localhost:8000/v1');
399
+ await act(async () => {
400
+ renderer.unmount();
401
+ });
402
+ });
403
+ test.serial('useProviderSelection - goBack returns wizard fields to the correct phase', async (t) => {
404
+ const customProviders = [
405
+ { name: 'existing-provider', type: 'openai-compatible', baseUrl: 'http://localhost:11434/v1' },
406
+ ];
407
+ const settingsService = createMockSettingsService(customProviders, 'existing-provider');
408
+ let hook;
409
+ let renderer;
410
+ await act(async () => {
411
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
412
+ settingsService,
413
+ onHookResult: (h) => {
414
+ hook = h;
415
+ },
416
+ })));
417
+ });
418
+ await act(async () => {
419
+ hook.open();
420
+ });
421
+ await flush();
422
+ await act(async () => {
423
+ const active = hook.getActiveItems();
424
+ const targetIdx = active.findIndex((item) => item.kind === 'provider' && item.id === 'existing-provider');
425
+ let moves = 0;
426
+ for (let i = 0; i < targetIdx; i++) {
427
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
428
+ moves++;
429
+ }
430
+ }
431
+ for (let i = 0; i < moves; i++) {
432
+ hook.moveDown();
433
+ }
434
+ });
435
+ await flush();
436
+ // Enter on a custom provider jumps straight to edit_fields.
437
+ await act(async () => {
438
+ hook.selectItem();
439
+ });
440
+ await flush();
441
+ t.is(hook.phase, 'edit_fields');
442
+ t.is(hook.selectedIndex, 0);
443
+ // Enter on the Name field opens the name editor.
444
+ await act(async () => {
445
+ hook.selectItem();
446
+ });
447
+ await flush();
448
+ t.is(hook.phase, 'wizard_name');
449
+ t.is(hook.selectedIndex, 0);
450
+ await act(async () => {
451
+ hook.goBack();
452
+ });
453
+ await flush();
454
+ t.is(hook.phase, 'edit_fields');
455
+ t.is(hook.selectedIndex, 0);
456
+ await act(async () => {
457
+ renderer.unmount();
458
+ });
459
+ });
460
+ test.serial('useProviderSelection - confirm discard appears when backing out of a modified wizard', async (t) => {
461
+ const settingsService = createMockSettingsService([], 'openai');
462
+ let hook;
463
+ let renderer;
464
+ await act(async () => {
465
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
466
+ settingsService,
467
+ onHookResult: (h) => {
468
+ hook = h;
469
+ },
470
+ })));
471
+ });
472
+ await act(async () => {
473
+ hook.open();
474
+ });
475
+ await flush();
476
+ await act(async () => {
477
+ const active = hook.getActiveItems();
478
+ const targetIdx = active.findIndex((item) => item.kind === 'add-provider');
479
+ let moves = 0;
480
+ for (let i = 0; i < targetIdx; i++) {
481
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
482
+ moves++;
483
+ }
484
+ }
485
+ for (let i = 0; i < moves; i++) {
486
+ hook.moveDown();
487
+ }
488
+ });
489
+ await flush();
490
+ await act(async () => {
491
+ hook.selectItem();
492
+ });
493
+ await flush();
494
+ t.is(hook.phase, 'wizard_name');
495
+ await act(async () => {
496
+ hook.handleTextInputSubmit('test-provider');
497
+ });
498
+ await flush();
499
+ t.is(hook.phase, 'wizard_type');
500
+ await act(async () => {
501
+ hook.goBack();
502
+ });
503
+ await flush();
504
+ t.is(hook.phase, 'confirm_discard');
505
+ t.is(hook.selectedIndex, 1);
506
+ await act(async () => {
507
+ hook.selectItem();
508
+ });
509
+ await flush();
510
+ t.is(hook.phase, 'wizard_type');
511
+ t.is(hook.selectedIndex, 0);
512
+ await act(async () => {
513
+ hook.goBack();
514
+ });
515
+ await flush();
516
+ t.is(hook.phase, 'confirm_discard');
517
+ await act(async () => {
518
+ hook.moveUp();
519
+ });
520
+ await flush();
521
+ await act(async () => {
522
+ hook.selectItem();
523
+ });
524
+ await flush();
525
+ t.is(hook.phase, 'wizard_name');
526
+ t.truthy(hook.draft);
527
+ await act(async () => {
528
+ renderer.unmount();
529
+ });
530
+ });
531
+ test.serial('useProviderSelection - back out of an unmodified wizard returns directly to the list', async (t) => {
532
+ const settingsService = createMockSettingsService([], 'openai');
533
+ let hook;
534
+ let renderer;
535
+ await act(async () => {
536
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
537
+ settingsService,
538
+ onHookResult: (h) => {
539
+ hook = h;
540
+ },
541
+ })));
542
+ });
543
+ await act(async () => {
544
+ hook.open();
545
+ });
546
+ await flush();
547
+ await act(async () => {
548
+ const active = hook.getActiveItems();
549
+ const targetIdx = active.findIndex((item) => item.kind === 'add-provider');
550
+ let moves = 0;
551
+ for (let i = 0; i < targetIdx; i++) {
552
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
553
+ moves++;
554
+ }
555
+ }
556
+ for (let i = 0; i < moves; i++) {
557
+ hook.moveDown();
558
+ }
559
+ });
560
+ await flush();
561
+ await act(async () => {
562
+ hook.selectItem();
563
+ });
564
+ await flush();
565
+ t.is(hook.phase, 'wizard_name');
566
+ await act(async () => {
567
+ hook.goBack();
568
+ });
569
+ await flush();
570
+ t.is(hook.phase, 'list');
571
+ await act(async () => {
572
+ renderer.unmount();
573
+ });
574
+ });
575
+ test.serial('useProviderSelection - saveDraft rejects duplicate names discovered after editing starts', async (t) => {
576
+ const settingsService = createMockSettingsService([], 'openai');
577
+ let hook;
578
+ let renderer;
579
+ await act(async () => {
580
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
581
+ settingsService,
582
+ onHookResult: (h) => {
583
+ hook = h;
584
+ },
585
+ })));
586
+ });
587
+ await act(async () => {
588
+ hook.open();
589
+ });
590
+ await flush();
591
+ await act(async () => {
592
+ const active = hook.getActiveItems();
593
+ const targetIdx = active.findIndex((item) => item.kind === 'add-provider');
594
+ let moves = 0;
595
+ for (let i = 0; i < targetIdx; i++) {
596
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
597
+ moves++;
598
+ }
599
+ }
600
+ for (let i = 0; i < moves; i++) {
601
+ hook.moveDown();
602
+ }
603
+ });
604
+ await flush();
605
+ await act(async () => {
606
+ hook.selectItem();
607
+ });
608
+ await flush();
609
+ t.is(hook.phase, 'wizard_name');
610
+ let success = false;
611
+ await act(async () => {
612
+ success = hook.handleTextInputSubmit('late-conflict-provider');
613
+ });
614
+ await flush();
615
+ t.true(success);
616
+ await act(async () => {
617
+ hook.selectItem();
618
+ });
619
+ await flush();
620
+ t.is(hook.phase, 'wizard_url');
621
+ t.is(hook.draft?.type, 'openai-compatible');
622
+ await act(async () => {
623
+ hook.handleTextInputSubmit('http://localhost:8000/v1');
624
+ });
625
+ await flush();
626
+ await act(async () => {
627
+ hook.handleTextInputSubmit('');
628
+ });
629
+ await flush();
630
+ settingsService.set('providers', [
631
+ { name: 'late-conflict-provider', type: 'openai-compatible', baseUrl: 'http://localhost:9000/v1' },
632
+ ]);
633
+ await act(async () => {
634
+ hook.selectItem();
635
+ });
636
+ await flush();
637
+ t.is(hook.phase, 'edit_fields');
638
+ t.deepEqual(hook.fieldErrors, { name: "Provider with name 'late-conflict-provider' already exists." });
639
+ t.is(hook.errorMessage, null);
640
+ t.is(settingsService.get('providers').length, 1);
641
+ await act(async () => {
642
+ renderer.unmount();
643
+ });
644
+ });
645
+ test.serial('useProviderSelection - unchanged provider names are allowed when editing', async (t) => {
646
+ const customProviders = [
647
+ { name: 'existing-provider', type: 'openai-compatible', baseUrl: 'http://localhost:11434/v1' },
648
+ ];
649
+ const settingsService = createMockSettingsService(customProviders, 'existing-provider');
650
+ let hook;
651
+ let renderer;
652
+ await act(async () => {
653
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
654
+ settingsService,
655
+ onHookResult: (h) => {
656
+ hook = h;
657
+ },
658
+ })));
659
+ });
660
+ await act(async () => {
661
+ hook.open();
662
+ });
663
+ await flush();
664
+ await act(async () => {
665
+ const active = hook.getActiveItems();
666
+ const targetIdx = active.findIndex((item) => item.kind === 'provider' && item.id === 'existing-provider');
667
+ let moves = 0;
668
+ for (let i = 0; i < targetIdx; i++) {
669
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
670
+ moves++;
671
+ }
672
+ }
673
+ for (let i = 0; i < moves; i++) {
674
+ hook.moveDown();
675
+ }
676
+ });
677
+ await flush();
678
+ // Enter on the custom provider goes directly to edit_fields.
679
+ await act(async () => {
680
+ hook.selectItem();
681
+ });
682
+ await flush();
683
+ // Enter on the Name field opens the name editor.
684
+ await act(async () => {
685
+ hook.selectItem();
686
+ });
687
+ await flush();
688
+ t.is(hook.phase, 'wizard_name');
689
+ let success = false;
690
+ await act(async () => {
691
+ success = hook.handleTextInputSubmit('existing-provider');
692
+ });
693
+ await flush();
694
+ t.true(success);
695
+ t.is(hook.phase, 'edit_fields');
696
+ await act(async () => {
697
+ hook.moveDown();
698
+ hook.moveDown();
699
+ hook.moveDown();
700
+ hook.moveDown();
701
+ });
702
+ await flush();
703
+ t.is(hook.selectedIndex, 4);
704
+ await act(async () => {
705
+ hook.selectItem();
706
+ });
707
+ await flush();
708
+ t.is(hook.phase, 'list');
709
+ t.is(hook.errorMessage, null);
710
+ t.is(settingsService.get('providers').length, 1);
711
+ t.is(settingsService.get('providers')[0].name, 'existing-provider');
712
+ await act(async () => {
713
+ renderer.unmount();
714
+ });
715
+ });
716
+ test.serial('useProviderSelection - scrollOffset updates when navigating and resets on phase change', async (t) => {
717
+ // Create many mock custom providers to exceed the scroll threshold of 10
718
+ const customProviders = Array.from({ length: 15 }, (_, i) => ({
719
+ name: `custom-provider-${i}`,
720
+ type: 'openai-compatible',
721
+ baseUrl: `http://localhost:1143${i}/v1`,
722
+ }));
723
+ const settingsService = createMockSettingsService(customProviders, 'openai');
724
+ let hook;
725
+ let renderer;
726
+ await act(async () => {
727
+ renderer = render(React.createElement(InputProvider, {}, React.createElement(TestComponent, {
728
+ settingsService,
729
+ onHookResult: (h) => {
730
+ hook = h;
731
+ },
732
+ })));
733
+ });
734
+ await act(async () => {
735
+ hook.open();
736
+ });
737
+ await flush();
738
+ t.is(hook.phase, 'list');
739
+ t.is(hook.scrollOffset, 0);
740
+ // Move down 10 times to reach index 11 (the 11th active item)
741
+ await act(async () => {
742
+ for (let i = 0; i < 10; i++) {
743
+ hook.moveDown();
744
+ }
745
+ });
746
+ await flush();
747
+ t.is(hook.selectedIndex, 11);
748
+ t.is(hook.scrollOffset, 2, 'scrollOffset should scroll to 2 when reaching index 11');
749
+ // Move down 1 more time to index 12 (the 12th active item)
750
+ await act(async () => {
751
+ hook.moveDown();
752
+ });
753
+ await flush();
754
+ t.is(hook.selectedIndex, 12);
755
+ t.is(hook.scrollOffset, 3, 'scrollOffset should scroll to 3 when reaching index 12');
756
+ // Move up to index 0 (scrollOffset should sync back)
757
+ await act(async () => {
758
+ for (let i = 0; i < 11; i++) {
759
+ hook.moveUp();
760
+ }
761
+ });
762
+ await flush();
763
+ t.is(hook.selectedIndex, 0);
764
+ t.is(hook.scrollOffset, 0, 'scrollOffset should return to 0 when moving back to the top');
765
+ // Transition to wizard_name phase to verify scrollOffset resets
766
+ // Let's select "Add Custom Provider" which is at the end of the list
767
+ await act(async () => {
768
+ const active = hook.getActiveItems();
769
+ const targetIdx = active.findIndex((item) => item.kind === 'add-provider');
770
+ let moves = 0;
771
+ for (let i = 0; i < targetIdx; i++) {
772
+ if (!(active[i].kind === 'provider' && active[i].id === 'codex')) {
773
+ moves++;
774
+ }
775
+ }
776
+ for (let i = 0; i < moves; i++) {
777
+ hook.moveDown();
778
+ }
779
+ });
780
+ await flush();
781
+ t.is(hook.selectedIndex, hook.items.length);
782
+ t.true(hook.scrollOffset > 0, 'scrollOffset should be non-zero near the end of the long list');
783
+ await act(async () => {
784
+ hook.selectItem();
785
+ });
786
+ await flush();
787
+ t.is(hook.phase, 'wizard_name');
788
+ t.is(hook.scrollOffset, 0, 'scrollOffset should reset to 0 when phase changes to wizard_name');
789
+ await act(async () => {
790
+ renderer.unmount();
791
+ });
792
+ });
793
+ //# sourceMappingURL=use-provider-selection.test.js.map