@kolisachint/hoocode-agent 0.4.162 → 0.4.164

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 (339) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/cli/args.d.ts +0 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +0 -31
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/config.d.ts +0 -40
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +4 -50
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-frontmatter.d.ts +0 -28
  11. package/dist/core/agent-frontmatter.d.ts.map +1 -1
  12. package/dist/core/agent-frontmatter.js +0 -6
  13. package/dist/core/agent-frontmatter.js.map +1 -1
  14. package/dist/core/agent-session-services.d.ts +0 -2
  15. package/dist/core/agent-session-services.d.ts.map +1 -1
  16. package/dist/core/agent-session-services.js +0 -2
  17. package/dist/core/agent-session-services.js.map +1 -1
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +0 -2
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/auth-guidance.d.ts +0 -1
  22. package/dist/core/auth-guidance.d.ts.map +1 -1
  23. package/dist/core/auth-guidance.js +1 -1
  24. package/dist/core/auth-guidance.js.map +1 -1
  25. package/dist/core/embsearch/chunker.d.ts +0 -2
  26. package/dist/core/embsearch/chunker.d.ts.map +1 -1
  27. package/dist/core/embsearch/chunker.js +1 -1
  28. package/dist/core/embsearch/chunker.js.map +1 -1
  29. package/dist/core/embsearch/index-meta.d.ts +0 -2
  30. package/dist/core/embsearch/index-meta.d.ts.map +1 -1
  31. package/dist/core/embsearch/index-meta.js +0 -6
  32. package/dist/core/embsearch/index-meta.js.map +1 -1
  33. package/dist/core/export-html/ansi-to-html.d.ts +0 -4
  34. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  35. package/dist/core/export-html/ansi-to-html.js +1 -1
  36. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  37. package/dist/core/extensions/index.d.ts +3 -3
  38. package/dist/core/extensions/index.d.ts.map +1 -1
  39. package/dist/core/extensions/index.js +1 -1
  40. package/dist/core/extensions/index.js.map +1 -1
  41. package/dist/core/extensions/plugins/authoring.d.ts +2 -2
  42. package/dist/core/extensions/plugins/authoring.d.ts.map +1 -1
  43. package/dist/core/extensions/plugins/authoring.js +0 -0
  44. package/dist/core/extensions/plugins/authoring.js.map +1 -1
  45. package/dist/core/extensions/plugins/formats/index.d.ts +2 -6
  46. package/dist/core/extensions/plugins/formats/index.d.ts.map +1 -1
  47. package/dist/core/extensions/plugins/formats/index.js +2 -3
  48. package/dist/core/extensions/plugins/formats/index.js.map +1 -1
  49. package/dist/core/extensions/plugins/formats/shared.d.ts +0 -2
  50. package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
  51. package/dist/core/extensions/plugins/formats/shared.js +1 -1
  52. package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
  53. package/dist/core/extensions/plugins/formats/types.d.ts +4 -3
  54. package/dist/core/extensions/plugins/formats/types.d.ts.map +1 -1
  55. package/dist/core/extensions/plugins/formats/types.js.map +1 -1
  56. package/dist/core/extensions/plugins/install.d.ts +0 -2
  57. package/dist/core/extensions/plugins/install.d.ts.map +1 -1
  58. package/dist/core/extensions/plugins/install.js +1 -1
  59. package/dist/core/extensions/plugins/install.js.map +1 -1
  60. package/dist/core/extensions/plugins/manifest.d.ts +0 -6
  61. package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
  62. package/dist/core/extensions/plugins/manifest.js +0 -6
  63. package/dist/core/extensions/plugins/manifest.js.map +1 -1
  64. package/dist/core/extensions/plugins/marketplace.d.ts +3 -3
  65. package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
  66. package/dist/core/extensions/plugins/marketplace.js.map +1 -1
  67. package/dist/core/extensions/types.d.ts +28 -28
  68. package/dist/core/extensions/types.d.ts.map +1 -1
  69. package/dist/core/extensions/types.js.map +1 -1
  70. package/dist/core/keybindings.d.ts +3 -3
  71. package/dist/core/keybindings.d.ts.map +1 -1
  72. package/dist/core/keybindings.js.map +1 -1
  73. package/dist/core/model-resolver.d.ts +0 -7
  74. package/dist/core/model-resolver.d.ts.map +1 -1
  75. package/dist/core/model-resolver.js +0 -56
  76. package/dist/core/model-resolver.js.map +1 -1
  77. package/dist/core/package-resource-discovery.d.ts +0 -10
  78. package/dist/core/package-resource-discovery.d.ts.map +1 -1
  79. package/dist/core/package-resource-discovery.js +7 -7
  80. package/dist/core/package-resource-discovery.js.map +1 -1
  81. package/dist/core/provider-health.d.ts +0 -2
  82. package/dist/core/provider-health.d.ts.map +1 -1
  83. package/dist/core/provider-health.js +1 -1
  84. package/dist/core/provider-health.js.map +1 -1
  85. package/dist/core/resolve-config-value.d.ts +0 -4
  86. package/dist/core/resolve-config-value.d.ts.map +1 -1
  87. package/dist/core/resolve-config-value.js +0 -15
  88. package/dist/core/resolve-config-value.js.map +1 -1
  89. package/dist/core/sdk.d.ts +3 -23
  90. package/dist/core/sdk.d.ts.map +1 -1
  91. package/dist/core/sdk.js +3 -5
  92. package/dist/core/sdk.js.map +1 -1
  93. package/dist/core/search/eval.d.ts +0 -3
  94. package/dist/core/search/eval.d.ts.map +1 -1
  95. package/dist/core/search/eval.js +1 -1
  96. package/dist/core/search/eval.js.map +1 -1
  97. package/dist/core/search/types.d.ts +2 -1
  98. package/dist/core/search/types.d.ts.map +1 -1
  99. package/dist/core/search/types.js.map +1 -1
  100. package/dist/core/session-cwd.d.ts +0 -1
  101. package/dist/core/session-cwd.d.ts.map +1 -1
  102. package/dist/core/session-cwd.js +1 -1
  103. package/dist/core/session-cwd.js.map +1 -1
  104. package/dist/core/settings-defaults.d.ts +0 -3
  105. package/dist/core/settings-defaults.d.ts.map +1 -1
  106. package/dist/core/settings-defaults.js +0 -3
  107. package/dist/core/settings-defaults.js.map +1 -1
  108. package/dist/core/settings-manager.d.ts +2 -9
  109. package/dist/core/settings-manager.d.ts.map +1 -1
  110. package/dist/core/settings-manager.js +0 -25
  111. package/dist/core/settings-manager.js.map +1 -1
  112. package/dist/core/settings-types.d.ts +9 -11
  113. package/dist/core/settings-types.d.ts.map +1 -1
  114. package/dist/core/settings-types.js.map +1 -1
  115. package/dist/core/subagent-inbox.d.ts +1 -3
  116. package/dist/core/subagent-inbox.d.ts.map +1 -1
  117. package/dist/core/subagent-inbox.js +1 -1
  118. package/dist/core/subagent-inbox.js.map +1 -1
  119. package/dist/core/subagent-pool.d.ts +0 -13
  120. package/dist/core/subagent-pool.d.ts.map +1 -1
  121. package/dist/core/subagent-pool.js +2 -2
  122. package/dist/core/subagent-pool.js.map +1 -1
  123. package/dist/core/task-store.d.ts +3 -3
  124. package/dist/core/task-store.d.ts.map +1 -1
  125. package/dist/core/task-store.js.map +1 -1
  126. package/dist/core/team-auto.d.ts +0 -2
  127. package/dist/core/team-auto.d.ts.map +1 -1
  128. package/dist/core/team-auto.js +1 -1
  129. package/dist/core/team-auto.js.map +1 -1
  130. package/dist/core/token-budget.d.ts +0 -13
  131. package/dist/core/token-budget.d.ts.map +1 -1
  132. package/dist/core/token-budget.js.map +1 -1
  133. package/dist/core/tools/bash.d.ts.map +1 -1
  134. package/dist/core/tools/bash.js +3 -1
  135. package/dist/core/tools/bash.js.map +1 -1
  136. package/dist/core/tools/edit-diff.d.ts +0 -38
  137. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  138. package/dist/core/tools/edit-diff.js +2 -9
  139. package/dist/core/tools/edit-diff.js.map +1 -1
  140. package/dist/core/tools/edit.d.ts.map +1 -1
  141. package/dist/core/tools/edit.js +5 -5
  142. package/dist/core/tools/edit.js.map +1 -1
  143. package/dist/core/tools/find.d.ts.map +1 -1
  144. package/dist/core/tools/find.js +8 -10
  145. package/dist/core/tools/find.js.map +1 -1
  146. package/dist/core/tools/grep.d.ts.map +1 -1
  147. package/dist/core/tools/grep.js +4 -4
  148. package/dist/core/tools/grep.js.map +1 -1
  149. package/dist/core/tools/index.d.ts +0 -24
  150. package/dist/core/tools/index.d.ts.map +1 -1
  151. package/dist/core/tools/index.js +0 -20
  152. package/dist/core/tools/index.js.map +1 -1
  153. package/dist/core/tools/ls.d.ts.map +1 -1
  154. package/dist/core/tools/ls.js +2 -2
  155. package/dist/core/tools/ls.js.map +1 -1
  156. package/dist/core/tools/native-search.d.ts +3 -2
  157. package/dist/core/tools/native-search.d.ts.map +1 -1
  158. package/dist/core/tools/native-search.js.map +1 -1
  159. package/dist/core/tools/path-utils.d.ts +0 -10
  160. package/dist/core/tools/path-utils.d.ts.map +1 -1
  161. package/dist/core/tools/path-utils.js +2 -2
  162. package/dist/core/tools/path-utils.js.map +1 -1
  163. package/dist/core/tools/plugins.d.ts +1 -20
  164. package/dist/core/tools/plugins.d.ts.map +1 -1
  165. package/dist/core/tools/plugins.js +2 -2
  166. package/dist/core/tools/plugins.js.map +1 -1
  167. package/dist/core/tools/propose-plugin.d.ts +0 -12
  168. package/dist/core/tools/propose-plugin.d.ts.map +1 -1
  169. package/dist/core/tools/propose-plugin.js +0 -1
  170. package/dist/core/tools/propose-plugin.js.map +1 -1
  171. package/dist/core/tools/read-dedup.d.ts +0 -2
  172. package/dist/core/tools/read-dedup.d.ts.map +1 -1
  173. package/dist/core/tools/read-dedup.js +1 -1
  174. package/dist/core/tools/read-dedup.js.map +1 -1
  175. package/dist/core/tools/read.d.ts.map +1 -1
  176. package/dist/core/tools/read.js +10 -8
  177. package/dist/core/tools/read.js.map +1 -1
  178. package/dist/core/tools/render-utils.d.ts +0 -5
  179. package/dist/core/tools/render-utils.d.ts.map +1 -1
  180. package/dist/core/tools/render-utils.js.map +1 -1
  181. package/dist/core/tools/search.d.ts +1 -4
  182. package/dist/core/tools/search.d.ts.map +1 -1
  183. package/dist/core/tools/search.js +6 -6
  184. package/dist/core/tools/search.js.map +1 -1
  185. package/dist/core/tools/webfetch.d.ts +1 -2
  186. package/dist/core/tools/webfetch.d.ts.map +1 -1
  187. package/dist/core/tools/webfetch.js.map +1 -1
  188. package/dist/core/tools/websearch.d.ts +1 -2
  189. package/dist/core/tools/websearch.d.ts.map +1 -1
  190. package/dist/core/tools/websearch.js.map +1 -1
  191. package/dist/core/tools/webtools-shared.d.ts +3 -5
  192. package/dist/core/tools/webtools-shared.d.ts.map +1 -1
  193. package/dist/core/tools/webtools-shared.js +1 -1
  194. package/dist/core/tools/webtools-shared.js.map +1 -1
  195. package/dist/core/warm-subagent-pool-instance.d.ts +0 -4
  196. package/dist/core/warm-subagent-pool-instance.d.ts.map +1 -1
  197. package/dist/core/warm-subagent-pool-instance.js +0 -10
  198. package/dist/core/warm-subagent-pool-instance.js.map +1 -1
  199. package/dist/core/wordmark.d.ts +1 -9
  200. package/dist/core/wordmark.d.ts.map +1 -1
  201. package/dist/core/wordmark.js +2 -26
  202. package/dist/core/wordmark.js.map +1 -1
  203. package/dist/extensions/core/config.d.ts +4 -3
  204. package/dist/extensions/core/config.d.ts.map +1 -1
  205. package/dist/extensions/core/config.js.map +1 -1
  206. package/dist/extensions/core/mcp-loader.d.ts +0 -18
  207. package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
  208. package/dist/extensions/core/mcp-loader.js.map +1 -1
  209. package/dist/extensions/core/prompt-reactive/policy.d.ts +0 -8
  210. package/dist/extensions/core/prompt-reactive/policy.d.ts.map +1 -1
  211. package/dist/extensions/core/prompt-reactive/policy.js +1 -1
  212. package/dist/extensions/core/prompt-reactive/policy.js.map +1 -1
  213. package/dist/main.d.ts.map +1 -1
  214. package/dist/main.js +0 -30
  215. package/dist/main.js.map +1 -1
  216. package/dist/migrations.d.ts +0 -16
  217. package/dist/migrations.d.ts.map +1 -1
  218. package/dist/migrations.js +2 -2
  219. package/dist/migrations.js.map +1 -1
  220. package/dist/modes/interactive/brand.d.ts +0 -4
  221. package/dist/modes/interactive/brand.d.ts.map +1 -1
  222. package/dist/modes/interactive/brand.js +0 -4
  223. package/dist/modes/interactive/brand.js.map +1 -1
  224. package/dist/modes/interactive/components/index.d.ts +0 -4
  225. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  226. package/dist/modes/interactive/components/index.js +0 -5
  227. package/dist/modes/interactive/components/index.js.map +1 -1
  228. package/dist/modes/interactive/components/read-output.d.ts +0 -9
  229. package/dist/modes/interactive/components/read-output.d.ts.map +1 -1
  230. package/dist/modes/interactive/components/read-output.js +0 -20
  231. package/dist/modes/interactive/components/read-output.js.map +1 -1
  232. package/dist/modes/interactive/components/session-selector-search.d.ts +0 -17
  233. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/session-selector-search.js +2 -2
  235. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  236. package/dist/modes/interactive/components/settings-selector.d.ts +5 -14
  237. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  238. package/dist/modes/interactive/components/settings-selector.js +2 -2
  239. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  240. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  241. package/dist/modes/interactive/interactive-mode.js +0 -18
  242. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  243. package/dist/modes/interactive/login-controller.d.ts +1 -1
  244. package/dist/modes/interactive/login-controller.d.ts.map +1 -1
  245. package/dist/modes/interactive/login-controller.js +1 -1
  246. package/dist/modes/interactive/login-controller.js.map +1 -1
  247. package/dist/modes/interactive/resource-display.d.ts +0 -20
  248. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  249. package/dist/modes/interactive/resource-display.js +7 -7
  250. package/dist/modes/interactive/resource-display.js.map +1 -1
  251. package/dist/modes/interactive/theme/theme.d.ts +0 -4
  252. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  253. package/dist/modes/interactive/theme/theme.js +0 -7
  254. package/dist/modes/interactive/theme/theme.js.map +1 -1
  255. package/dist/modes/rpc/rpc-mode.d.ts +0 -1
  256. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  257. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  258. package/dist/modes/rpc/rpc-types.d.ts +0 -1
  259. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  260. package/dist/modes/rpc/rpc-types.js +3 -0
  261. package/dist/modes/rpc/rpc-types.js.map +1 -1
  262. package/dist/package-manager-cli.d.ts +0 -1
  263. package/dist/package-manager-cli.d.ts.map +1 -1
  264. package/dist/package-manager-cli.js.map +1 -1
  265. package/dist/utils/changelog.d.ts +0 -4
  266. package/dist/utils/changelog.d.ts.map +1 -1
  267. package/dist/utils/changelog.js +1 -1
  268. package/dist/utils/changelog.js.map +1 -1
  269. package/dist/utils/clipboard-native.d.ts +1 -1
  270. package/dist/utils/clipboard-native.d.ts.map +1 -1
  271. package/dist/utils/clipboard-native.js.map +1 -1
  272. package/dist/utils/paths.d.ts +0 -2
  273. package/dist/utils/paths.d.ts.map +1 -1
  274. package/dist/utils/paths.js +1 -1
  275. package/dist/utils/paths.js.map +1 -1
  276. package/dist/utils/plan/parser.d.ts +5 -4
  277. package/dist/utils/plan/parser.d.ts.map +1 -1
  278. package/dist/utils/plan/parser.js.map +1 -1
  279. package/dist/utils/tools-manager.d.ts +1 -2
  280. package/dist/utils/tools-manager.d.ts.map +1 -1
  281. package/dist/utils/tools-manager.js +2 -50
  282. package/dist/utils/tools-manager.js.map +1 -1
  283. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  284. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  285. package/examples/extensions/sandbox/package.json +1 -1
  286. package/examples/extensions/with-deps/package.json +1 -1
  287. package/package.json +4 -7
  288. package/dist/core/index.d.ts +0 -12
  289. package/dist/core/index.d.ts.map +0 -1
  290. package/dist/core/index.js +0 -12
  291. package/dist/core/index.js.map +0 -1
  292. package/dist/core/tools/browser/browser-continue.d.ts +0 -25
  293. package/dist/core/tools/browser/browser-continue.d.ts.map +0 -1
  294. package/dist/core/tools/browser/browser-continue.js +0 -84
  295. package/dist/core/tools/browser/browser-continue.js.map +0 -1
  296. package/dist/core/tools/browser/browser-run.d.ts +0 -48
  297. package/dist/core/tools/browser/browser-run.d.ts.map +0 -1
  298. package/dist/core/tools/browser/browser-run.js +0 -377
  299. package/dist/core/tools/browser/browser-run.js.map +0 -1
  300. package/dist/core/tools/browser/browsertools-shared.d.ts +0 -190
  301. package/dist/core/tools/browser/browsertools-shared.d.ts.map +0 -1
  302. package/dist/core/tools/browser/browsertools-shared.js +0 -335
  303. package/dist/core/tools/browser/browsertools-shared.js.map +0 -1
  304. package/dist/core/tools/browser/index.d.ts +0 -8
  305. package/dist/core/tools/browser/index.d.ts.map +0 -1
  306. package/dist/core/tools/browser/index.js +0 -8
  307. package/dist/core/tools/browser/index.js.map +0 -1
  308. package/dist/core/tools/doc/docedit.d.ts +0 -44
  309. package/dist/core/tools/doc/docedit.d.ts.map +0 -1
  310. package/dist/core/tools/doc/docedit.js +0 -133
  311. package/dist/core/tools/doc/docedit.js.map +0 -1
  312. package/dist/core/tools/doc/docgrep.d.ts +0 -28
  313. package/dist/core/tools/doc/docgrep.d.ts.map +0 -1
  314. package/dist/core/tools/doc/docgrep.js +0 -106
  315. package/dist/core/tools/doc/docgrep.js.map +0 -1
  316. package/dist/core/tools/doc/docpeek.d.ts +0 -25
  317. package/dist/core/tools/doc/docpeek.d.ts.map +0 -1
  318. package/dist/core/tools/doc/docpeek.js +0 -112
  319. package/dist/core/tools/doc/docpeek.js.map +0 -1
  320. package/dist/core/tools/doc/docread.d.ts +0 -32
  321. package/dist/core/tools/doc/docread.d.ts.map +0 -1
  322. package/dist/core/tools/doc/docread.js +0 -113
  323. package/dist/core/tools/doc/docread.js.map +0 -1
  324. package/dist/core/tools/doc/docscan.d.ts +0 -29
  325. package/dist/core/tools/doc/docscan.d.ts.map +0 -1
  326. package/dist/core/tools/doc/docscan.js +0 -110
  327. package/dist/core/tools/doc/docscan.js.map +0 -1
  328. package/dist/core/tools/doc/docwrite.d.ts +0 -44
  329. package/dist/core/tools/doc/docwrite.d.ts.map +0 -1
  330. package/dist/core/tools/doc/docwrite.js +0 -102
  331. package/dist/core/tools/doc/docwrite.js.map +0 -1
  332. package/dist/core/tools/doc/index.d.ts +0 -11
  333. package/dist/core/tools/doc/index.d.ts.map +0 -1
  334. package/dist/core/tools/doc/index.js +0 -11
  335. package/dist/core/tools/doc/index.js.map +0 -1
  336. package/dist/core/tools/filetools-shared.d.ts +0 -271
  337. package/dist/core/tools/filetools-shared.d.ts.map +0 -1
  338. package/dist/core/tools/filetools-shared.js +0 -403
  339. package/dist/core/tools/filetools-shared.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  * Auth / login flows for the interactive mode (/login, /logout).
3
3
  *
4
4
  * Owns the provider auth-type selector, the OAuth and API-key login dialogs,
5
- * the Bedrock setup notice, and post-login model selection. Extracted from
5
+ * and post-login model selection. Extracted from
6
6
  * interactive-mode.ts behind a narrow LoginControllerDeps interface; the
7
7
  * `/login` and `/logout` commands call `showOAuthSelector`.
8
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"login-controller.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/login-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAgB,KAAK,KAAK,EAAgD,MAAM,yBAAyB,CAAC;AACjH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAShE,wBAAgB,qBAAqB,CACpC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,EACrC,kBAAkB,GAAE,WAAW,CAAC,MAAM,CAA4B,GAChE,OAAO,CAQT;AAUD,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,GAAG,CAAC;IACR,0EAA0E;IAC1E,IAAI,OAAO,IAAI,YAAY,CAAC;IAC5B,wEAAwE;IACxE,SAAS,IAAI,SAAS,CAAC;IACvB,gEAAgE;IAChE,eAAe,EAAE,SAAS,CAAC;IAC3B,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,uDAAuD;IACvD,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,uBAAuB,IAAI,IAAI,CAAC;IAChC,gBAAgB,IAAI,IAAI,CAAC;IACzB,uCAAuC,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED,qBAAa,eAAe;IACf,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,mBAAmB,EAAI;IAE1D,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,uBAAuB;IA0B/B,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,yBAAyB;IAqBjC,OAAO,CAAC,yBAAyB;IAsC3B,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C/D;YAEa,8BAA8B;YAoD9B,qBAAqB;IAoCnC,OAAO,CAAC,oBAAoB;YA4Bd,eAAe;CAuF7B","sourcesContent":["/**\n * Auth / login flows for the interactive mode (/login, /logout).\n *\n * Owns the provider auth-type selector, the OAuth and API-key login dialogs,\n * the Bedrock setup notice, and post-login model selection. Extracted from\n * interactive-mode.ts behind a narrow LoginControllerDeps interface; the\n * `/login` and `/logout` commands call `showOAuthSelector`.\n */\n\nimport { getProviders, type Model, type OAuthProviderId, type OAuthSelectPrompt } from \"@kolisachint/hoocode-ai\";\nimport type { Component, Container, TUI } from \"@kolisachint/hoocode-tui\";\nimport { getAuthPath } from \"../../config.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport { defaultModelPerProvider } from \"../../core/model-resolver.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"../../core/provider-display-names.js\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.js\";\nimport { LoginDialogComponent } from \"./components/login-dialog.js\";\nimport { type AuthSelectorProvider, OAuthSelectorComponent } from \"./components/oauth-selector.js\";\n\nconst BUILT_IN_MODEL_PROVIDERS = new Set<string>(getProviders());\n\nexport function isApiKeyLoginProvider(\n\tproviderId: string,\n\toauthProviderIds: ReadonlySet<string>,\n\tbuiltInProviderIds: ReadonlySet<string> = BUILT_IN_MODEL_PROVIDERS,\n): boolean {\n\tif (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {\n\t\treturn true;\n\t}\n\tif (builtInProviderIds.has(providerId)) {\n\t\treturn false;\n\t}\n\treturn !oauthProviderIds.has(providerId);\n}\n\nfunction isUnknownModel(model: Model<any> | undefined): boolean {\n\treturn !!model && model.provider === \"unknown\" && model.id === \"unknown\" && model.api === \"unknown\";\n}\n\nfunction hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider {\n\treturn providerId in defaultModelPerProvider;\n}\n\n/** The slice of the interactive mode the login flows need. */\nexport interface LoginControllerDeps {\n\tui: TUI;\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\t/** The prompt editor (read at call time; the editor can be swapped). */\n\tgetEditor(): Component;\n\t/** Container that hosts the prompt editor and login dialogs. */\n\teditorContainer: Container;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n\t/** Recompute the footer's available provider count. */\n\tupdateAvailableProviderCount(): Promise<void>;\n\tupdateEditorBorderColor(): void;\n\tinvalidateFooter(): void;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n}\n\nexport class LoginController {\n\tconstructor(private readonly deps: LoginControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\tprivate restoreEditor(): void {\n\t\tconst { deps } = this;\n\t\tdeps.editorContainer.clear();\n\t\tdeps.editorContainer.addChild(deps.getEditor());\n\t\tdeps.ui.setFocus(deps.getEditor());\n\t\tdeps.ui.requestRender();\n\t}\n\n\tprivate getLoginProviderOptions(authType?: \"oauth\" | \"api_key\"): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst oauthProviders = authStorage.getOAuthProviders();\n\t\tconst oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));\n\t\tconst options: AuthSelectorProvider[] = oauthProviders.map((provider) => ({\n\t\t\tid: provider.id,\n\t\t\tname: provider.name,\n\t\t\tauthType: \"oauth\",\n\t\t}));\n\n\t\tconst modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));\n\t\tfor (const providerId of modelProviders) {\n\t\t\tif (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: \"api_key\",\n\t\t\t});\n\t\t}\n\n\t\tconst filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;\n\t\treturn filteredOptions.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate getLogoutProviderOptions(): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst options: AuthSelectorProvider[] = [];\n\n\t\tfor (const providerId of authStorage.list()) {\n\t\t\tconst credential = authStorage.get(providerId);\n\t\t\tif (!credential) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: credential.type,\n\t\t\t});\n\t\t}\n\n\t\treturn options.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate showLoginAuthTypeSelector(): void {\n\t\tconst subscriptionLabel = \"Use a subscription\";\n\t\tconst apiKeyLabel = \"Use an API key\";\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\t\"Select authentication method:\",\n\t\t\t\t[subscriptionLabel, apiKeyLabel],\n\t\t\t\t(option) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tconst authType = option === subscriptionLabel ? \"oauth\" : \"api_key\";\n\t\t\t\t\tthis.showLoginProviderSelector(authType);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate showLoginProviderSelector(authType: \"oauth\" | \"api_key\"): void {\n\t\tconst providerOptions = this.getLoginProviderOptions(authType);\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\tauthType === \"oauth\" ? \"No subscription providers available.\" : \"No API key providers available.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\t\"login\",\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (providerOption.authType === \"oauth\") {\n\t\t\t\t\t\tawait this.showLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.showApiKeyLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\t\t},\n\t\t\t\t(providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId),\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tasync showOAuthSelector(mode: \"login\" | \"logout\"): Promise<void> {\n\t\tif (mode === \"login\") {\n\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst providerOptions = this.getLogoutProviderOptions();\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\t\"No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\tmode,\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.session.modelRegistry.authStorage.logout(providerOption.id);\n\t\t\t\t\t\tthis.session.modelRegistry.refresh();\n\t\t\t\t\t\tawait this.deps.updateAvailableProviderCount();\n\t\t\t\t\t\tconst message =\n\t\t\t\t\t\t\tproviderOption.authType === \"oauth\"\n\t\t\t\t\t\t\t\t? `Logged out of ${providerOption.name}`\n\t\t\t\t\t\t\t\t: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;\n\t\t\t\t\t\tthis.deps.showStatus(message);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tthis.deps.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate async completeProviderAuthentication(\n\t\tproviderId: string,\n\t\tproviderName: string,\n\t\tauthType: \"oauth\" | \"api_key\",\n\t\tpreviousModel: Model<any> | undefined,\n\t): Promise<void> {\n\t\tthis.session.modelRegistry.refresh();\n\n\t\tconst actionLabel = authType === \"oauth\" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;\n\n\t\tlet selectedModel: Model<any> | undefined;\n\t\tlet selectionError: string | undefined;\n\t\tif (isUnknownModel(previousModel)) {\n\t\t\tconst availableModels = this.session.modelRegistry.getAvailable();\n\t\t\tconst providerModels = availableModels.filter((model) => model.provider === providerId);\n\t\t\tif (!hasDefaultModelProvider(providerId)) {\n\t\t\t\tselectionError = `${actionLabel}, but no default model is configured for provider \"${providerId}\". Use /model to select a model.`;\n\t\t\t} else if (providerModels.length === 0) {\n\t\t\t\tselectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;\n\t\t\t} else {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[providerId];\n\t\t\t\tselectedModel = providerModels.find((model) => model.id === defaultModelId);\n\t\t\t\tif (!selectedModel) {\n\t\t\t\t\tselectionError = `${actionLabel}, but its default model \"${defaultModelId}\" is not available. Use /model to select a model.`;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.session.setModel(selectedModel);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tselectedModel = undefined;\n\t\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tselectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.deps.updateAvailableProviderCount();\n\t\tthis.deps.invalidateFooter();\n\t\tthis.deps.updateEditorBorderColor();\n\t\tif (selectedModel) {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);\n\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);\n\t\t} else {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);\n\t\t\tif (selectionError) {\n\t\t\t\tthis.deps.showError(selectionError);\n\t\t\t} else {\n\t\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async showApiKeyLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst previousModel = this.session.model;\n\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\ttry {\n\t\t\tconst apiKey = (await dialog.showPrompt(\"Enter API key:\")).trim();\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"API key cannot be empty.\");\n\t\t\t}\n\n\t\t\tthis.session.modelRegistry.authStorage.set(providerId, { type: \"api_key\", key: apiKey });\n\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"api_key\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showOAuthLoginSelect(dialog: LoginDialogComponent, prompt: OAuthSelectPrompt): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst restoreDialog = () => {\n\t\t\t\tthis.deps.editorContainer.clear();\n\t\t\t\tthis.deps.editorContainer.addChild(dialog);\n\t\t\t\tthis.deps.ui.setFocus(dialog);\n\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t};\n\t\t\tconst labels = prompt.options.map((option) => option.label);\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\tprompt.message,\n\t\t\t\tlabels,\n\t\t\t\t(optionLabel) => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(prompt.options.find((option) => option.label === optionLabel)?.id);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.deps.editorContainer.clear();\n\t\t\tthis.deps.editorContainer.addChild(selector);\n\t\t\tthis.deps.ui.setFocus(selector);\n\t\t\tthis.deps.ui.requestRender();\n\t\t});\n\t}\n\n\tprivate async showLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst providerInfo = this.session.modelRegistry.authStorage\n\t\t\t.getOAuthProviders()\n\t\t\t.find((provider) => provider.id === providerId);\n\t\tconst previousModel = this.session.model;\n\n\t\t// Providers that use callback servers (can paste redirect URL)\n\t\tconst usesCallbackServer = providerInfo?.usesCallbackServer ?? false;\n\n\t\t// Create login dialog component\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\t// Show dialog in editor container\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\t// Promise for manual code input (racing with callback server)\n\t\tlet manualCodeResolve: ((code: string) => void) | undefined;\n\t\tlet manualCodeReject: ((err: Error) => void) | undefined;\n\t\tconst manualCodePromise = new Promise<string>((resolve, reject) => {\n\t\t\tmanualCodeResolve = resolve;\n\t\t\tmanualCodeReject = reject;\n\t\t});\n\n\t\ttry {\n\t\t\tawait this.session.modelRegistry.authStorage.login(providerId as OAuthProviderId, {\n\t\t\t\tonAuth: (info: { url: string; instructions?: string }) => {\n\t\t\t\t\tdialog.showAuth(info.url, info.instructions);\n\n\t\t\t\t\tif (usesCallbackServer) {\n\t\t\t\t\t\t// Show input for manual paste, racing with callback\n\t\t\t\t\t\tdialog\n\t\t\t\t\t\t\t.showManualInput(\"Paste redirect URL below, or complete login in browser:\")\n\t\t\t\t\t\t\t.then((value) => {\n\t\t\t\t\t\t\t\tif (value && manualCodeResolve) {\n\t\t\t\t\t\t\t\t\tmanualCodeResolve(value);\n\t\t\t\t\t\t\t\t\tmanualCodeResolve = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t\tif (manualCodeReject) {\n\t\t\t\t\t\t\t\t\tmanualCodeReject(new Error(\"Login cancelled\"));\n\t\t\t\t\t\t\t\t\tmanualCodeReject = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t} else if (providerId === \"github-copilot\") {\n\t\t\t\t\t\t// GitHub Copilot polls after onAuth\n\t\t\t\t\t\tdialog.showWaiting(\"Waiting for browser authentication...\");\n\t\t\t\t\t}\n\t\t\t\t\t// For Anthropic: onPrompt is called immediately after\n\t\t\t\t},\n\n\t\t\t\tonPrompt: async (prompt: { message: string; placeholder?: string }) => {\n\t\t\t\t\treturn dialog.showPrompt(prompt.message, prompt.placeholder);\n\t\t\t\t},\n\n\t\t\t\tonProgress: (message: string) => {\n\t\t\t\t\tdialog.showProgress(message);\n\t\t\t\t},\n\n\t\t\t\tonSelect: (prompt: OAuthSelectPrompt) => this.showOAuthLoginSelect(dialog, prompt),\n\n\t\t\t\tonManualCodeInput: () => manualCodePromise,\n\n\t\t\t\tsignal: dialog.signal,\n\t\t\t});\n\n\t\t\t// Success\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"oauth\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to login to ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"login-controller.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/login-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAgB,KAAK,KAAK,EAAgD,MAAM,yBAAyB,CAAC;AACjH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAShE,wBAAgB,qBAAqB,CACpC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,EACrC,kBAAkB,GAAE,WAAW,CAAC,MAAM,CAA4B,GAChE,OAAO,CAQT;AAUD,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,GAAG,CAAC;IACR,0EAA0E;IAC1E,IAAI,OAAO,IAAI,YAAY,CAAC;IAC5B,wEAAwE;IACxE,SAAS,IAAI,SAAS,CAAC;IACvB,gEAAgE;IAChE,eAAe,EAAE,SAAS,CAAC;IAC3B,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,uDAAuD;IACvD,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,uBAAuB,IAAI,IAAI,CAAC;IAChC,gBAAgB,IAAI,IAAI,CAAC;IACzB,uCAAuC,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED,qBAAa,eAAe;IACf,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,mBAAmB,EAAI;IAE1D,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,uBAAuB;IA0B/B,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,yBAAyB;IAqBjC,OAAO,CAAC,yBAAyB;IAsC3B,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C/D;YAEa,8BAA8B;YAoD9B,qBAAqB;IAoCnC,OAAO,CAAC,oBAAoB;YA4Bd,eAAe;CAuF7B","sourcesContent":["/**\n * Auth / login flows for the interactive mode (/login, /logout).\n *\n * Owns the provider auth-type selector, the OAuth and API-key login dialogs,\n * and post-login model selection. Extracted from\n * interactive-mode.ts behind a narrow LoginControllerDeps interface; the\n * `/login` and `/logout` commands call `showOAuthSelector`.\n */\n\nimport { getProviders, type Model, type OAuthProviderId, type OAuthSelectPrompt } from \"@kolisachint/hoocode-ai\";\nimport type { Component, Container, TUI } from \"@kolisachint/hoocode-tui\";\nimport { getAuthPath } from \"../../config.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport { defaultModelPerProvider } from \"../../core/model-resolver.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"../../core/provider-display-names.js\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.js\";\nimport { LoginDialogComponent } from \"./components/login-dialog.js\";\nimport { type AuthSelectorProvider, OAuthSelectorComponent } from \"./components/oauth-selector.js\";\n\nconst BUILT_IN_MODEL_PROVIDERS = new Set<string>(getProviders());\n\nexport function isApiKeyLoginProvider(\n\tproviderId: string,\n\toauthProviderIds: ReadonlySet<string>,\n\tbuiltInProviderIds: ReadonlySet<string> = BUILT_IN_MODEL_PROVIDERS,\n): boolean {\n\tif (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {\n\t\treturn true;\n\t}\n\tif (builtInProviderIds.has(providerId)) {\n\t\treturn false;\n\t}\n\treturn !oauthProviderIds.has(providerId);\n}\n\nfunction isUnknownModel(model: Model<any> | undefined): boolean {\n\treturn !!model && model.provider === \"unknown\" && model.id === \"unknown\" && model.api === \"unknown\";\n}\n\nfunction hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider {\n\treturn providerId in defaultModelPerProvider;\n}\n\n/** The slice of the interactive mode the login flows need. */\nexport interface LoginControllerDeps {\n\tui: TUI;\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\t/** The prompt editor (read at call time; the editor can be swapped). */\n\tgetEditor(): Component;\n\t/** Container that hosts the prompt editor and login dialogs. */\n\teditorContainer: Container;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n\t/** Recompute the footer's available provider count. */\n\tupdateAvailableProviderCount(): Promise<void>;\n\tupdateEditorBorderColor(): void;\n\tinvalidateFooter(): void;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n}\n\nexport class LoginController {\n\tconstructor(private readonly deps: LoginControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\tprivate restoreEditor(): void {\n\t\tconst { deps } = this;\n\t\tdeps.editorContainer.clear();\n\t\tdeps.editorContainer.addChild(deps.getEditor());\n\t\tdeps.ui.setFocus(deps.getEditor());\n\t\tdeps.ui.requestRender();\n\t}\n\n\tprivate getLoginProviderOptions(authType?: \"oauth\" | \"api_key\"): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst oauthProviders = authStorage.getOAuthProviders();\n\t\tconst oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));\n\t\tconst options: AuthSelectorProvider[] = oauthProviders.map((provider) => ({\n\t\t\tid: provider.id,\n\t\t\tname: provider.name,\n\t\t\tauthType: \"oauth\",\n\t\t}));\n\n\t\tconst modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));\n\t\tfor (const providerId of modelProviders) {\n\t\t\tif (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: \"api_key\",\n\t\t\t});\n\t\t}\n\n\t\tconst filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;\n\t\treturn filteredOptions.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate getLogoutProviderOptions(): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst options: AuthSelectorProvider[] = [];\n\n\t\tfor (const providerId of authStorage.list()) {\n\t\t\tconst credential = authStorage.get(providerId);\n\t\t\tif (!credential) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: credential.type,\n\t\t\t});\n\t\t}\n\n\t\treturn options.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate showLoginAuthTypeSelector(): void {\n\t\tconst subscriptionLabel = \"Use a subscription\";\n\t\tconst apiKeyLabel = \"Use an API key\";\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\t\"Select authentication method:\",\n\t\t\t\t[subscriptionLabel, apiKeyLabel],\n\t\t\t\t(option) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tconst authType = option === subscriptionLabel ? \"oauth\" : \"api_key\";\n\t\t\t\t\tthis.showLoginProviderSelector(authType);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate showLoginProviderSelector(authType: \"oauth\" | \"api_key\"): void {\n\t\tconst providerOptions = this.getLoginProviderOptions(authType);\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\tauthType === \"oauth\" ? \"No subscription providers available.\" : \"No API key providers available.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\t\"login\",\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (providerOption.authType === \"oauth\") {\n\t\t\t\t\t\tawait this.showLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.showApiKeyLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\t\t},\n\t\t\t\t(providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId),\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tasync showOAuthSelector(mode: \"login\" | \"logout\"): Promise<void> {\n\t\tif (mode === \"login\") {\n\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst providerOptions = this.getLogoutProviderOptions();\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\t\"No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\tmode,\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.session.modelRegistry.authStorage.logout(providerOption.id);\n\t\t\t\t\t\tthis.session.modelRegistry.refresh();\n\t\t\t\t\t\tawait this.deps.updateAvailableProviderCount();\n\t\t\t\t\t\tconst message =\n\t\t\t\t\t\t\tproviderOption.authType === \"oauth\"\n\t\t\t\t\t\t\t\t? `Logged out of ${providerOption.name}`\n\t\t\t\t\t\t\t\t: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;\n\t\t\t\t\t\tthis.deps.showStatus(message);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tthis.deps.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate async completeProviderAuthentication(\n\t\tproviderId: string,\n\t\tproviderName: string,\n\t\tauthType: \"oauth\" | \"api_key\",\n\t\tpreviousModel: Model<any> | undefined,\n\t): Promise<void> {\n\t\tthis.session.modelRegistry.refresh();\n\n\t\tconst actionLabel = authType === \"oauth\" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;\n\n\t\tlet selectedModel: Model<any> | undefined;\n\t\tlet selectionError: string | undefined;\n\t\tif (isUnknownModel(previousModel)) {\n\t\t\tconst availableModels = this.session.modelRegistry.getAvailable();\n\t\t\tconst providerModels = availableModels.filter((model) => model.provider === providerId);\n\t\t\tif (!hasDefaultModelProvider(providerId)) {\n\t\t\t\tselectionError = `${actionLabel}, but no default model is configured for provider \"${providerId}\". Use /model to select a model.`;\n\t\t\t} else if (providerModels.length === 0) {\n\t\t\t\tselectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;\n\t\t\t} else {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[providerId];\n\t\t\t\tselectedModel = providerModels.find((model) => model.id === defaultModelId);\n\t\t\t\tif (!selectedModel) {\n\t\t\t\t\tselectionError = `${actionLabel}, but its default model \"${defaultModelId}\" is not available. Use /model to select a model.`;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.session.setModel(selectedModel);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tselectedModel = undefined;\n\t\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tselectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.deps.updateAvailableProviderCount();\n\t\tthis.deps.invalidateFooter();\n\t\tthis.deps.updateEditorBorderColor();\n\t\tif (selectedModel) {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);\n\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);\n\t\t} else {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);\n\t\t\tif (selectionError) {\n\t\t\t\tthis.deps.showError(selectionError);\n\t\t\t} else {\n\t\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async showApiKeyLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst previousModel = this.session.model;\n\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\ttry {\n\t\t\tconst apiKey = (await dialog.showPrompt(\"Enter API key:\")).trim();\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"API key cannot be empty.\");\n\t\t\t}\n\n\t\t\tthis.session.modelRegistry.authStorage.set(providerId, { type: \"api_key\", key: apiKey });\n\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"api_key\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showOAuthLoginSelect(dialog: LoginDialogComponent, prompt: OAuthSelectPrompt): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst restoreDialog = () => {\n\t\t\t\tthis.deps.editorContainer.clear();\n\t\t\t\tthis.deps.editorContainer.addChild(dialog);\n\t\t\t\tthis.deps.ui.setFocus(dialog);\n\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t};\n\t\t\tconst labels = prompt.options.map((option) => option.label);\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\tprompt.message,\n\t\t\t\tlabels,\n\t\t\t\t(optionLabel) => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(prompt.options.find((option) => option.label === optionLabel)?.id);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.deps.editorContainer.clear();\n\t\t\tthis.deps.editorContainer.addChild(selector);\n\t\t\tthis.deps.ui.setFocus(selector);\n\t\t\tthis.deps.ui.requestRender();\n\t\t});\n\t}\n\n\tprivate async showLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst providerInfo = this.session.modelRegistry.authStorage\n\t\t\t.getOAuthProviders()\n\t\t\t.find((provider) => provider.id === providerId);\n\t\tconst previousModel = this.session.model;\n\n\t\t// Providers that use callback servers (can paste redirect URL)\n\t\tconst usesCallbackServer = providerInfo?.usesCallbackServer ?? false;\n\n\t\t// Create login dialog component\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\t// Show dialog in editor container\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\t// Promise for manual code input (racing with callback server)\n\t\tlet manualCodeResolve: ((code: string) => void) | undefined;\n\t\tlet manualCodeReject: ((err: Error) => void) | undefined;\n\t\tconst manualCodePromise = new Promise<string>((resolve, reject) => {\n\t\t\tmanualCodeResolve = resolve;\n\t\t\tmanualCodeReject = reject;\n\t\t});\n\n\t\ttry {\n\t\t\tawait this.session.modelRegistry.authStorage.login(providerId as OAuthProviderId, {\n\t\t\t\tonAuth: (info: { url: string; instructions?: string }) => {\n\t\t\t\t\tdialog.showAuth(info.url, info.instructions);\n\n\t\t\t\t\tif (usesCallbackServer) {\n\t\t\t\t\t\t// Show input for manual paste, racing with callback\n\t\t\t\t\t\tdialog\n\t\t\t\t\t\t\t.showManualInput(\"Paste redirect URL below, or complete login in browser:\")\n\t\t\t\t\t\t\t.then((value) => {\n\t\t\t\t\t\t\t\tif (value && manualCodeResolve) {\n\t\t\t\t\t\t\t\t\tmanualCodeResolve(value);\n\t\t\t\t\t\t\t\t\tmanualCodeResolve = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t\tif (manualCodeReject) {\n\t\t\t\t\t\t\t\t\tmanualCodeReject(new Error(\"Login cancelled\"));\n\t\t\t\t\t\t\t\t\tmanualCodeReject = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t} else if (providerId === \"github-copilot\") {\n\t\t\t\t\t\t// GitHub Copilot polls after onAuth\n\t\t\t\t\t\tdialog.showWaiting(\"Waiting for browser authentication...\");\n\t\t\t\t\t}\n\t\t\t\t\t// For Anthropic: onPrompt is called immediately after\n\t\t\t\t},\n\n\t\t\t\tonPrompt: async (prompt: { message: string; placeholder?: string }) => {\n\t\t\t\t\treturn dialog.showPrompt(prompt.message, prompt.placeholder);\n\t\t\t\t},\n\n\t\t\t\tonProgress: (message: string) => {\n\t\t\t\t\tdialog.showProgress(message);\n\t\t\t\t},\n\n\t\t\t\tonSelect: (prompt: OAuthSelectPrompt) => this.showOAuthLoginSelect(dialog, prompt),\n\n\t\t\t\tonManualCodeInput: () => manualCodePromise,\n\n\t\t\t\tsignal: dialog.signal,\n\t\t\t});\n\n\t\t\t// Success\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"oauth\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to login to ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -2,7 +2,7 @@
2
2
  * Auth / login flows for the interactive mode (/login, /logout).
3
3
  *
4
4
  * Owns the provider auth-type selector, the OAuth and API-key login dialogs,
5
- * the Bedrock setup notice, and post-login model selection. Extracted from
5
+ * and post-login model selection. Extracted from
6
6
  * interactive-mode.ts behind a narrow LoginControllerDeps interface; the
7
7
  * `/login` and `/logout` commands call `showOAuthSelector`.
8
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"login-controller.js","sourceRoot":"","sources":["../../../src/modes/interactive/login-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Auth / login flows for the interactive mode (/login, /logout).\n *\n * Owns the provider auth-type selector, the OAuth and API-key login dialogs,\n * the Bedrock setup notice, and post-login model selection. Extracted from\n * interactive-mode.ts behind a narrow LoginControllerDeps interface; the\n * `/login` and `/logout` commands call `showOAuthSelector`.\n */\n\nimport { getProviders, type Model, type OAuthProviderId, type OAuthSelectPrompt } from \"@kolisachint/hoocode-ai\";\nimport type { Component, Container, TUI } from \"@kolisachint/hoocode-tui\";\nimport { getAuthPath } from \"../../config.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport { defaultModelPerProvider } from \"../../core/model-resolver.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"../../core/provider-display-names.js\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.js\";\nimport { LoginDialogComponent } from \"./components/login-dialog.js\";\nimport { type AuthSelectorProvider, OAuthSelectorComponent } from \"./components/oauth-selector.js\";\n\nconst BUILT_IN_MODEL_PROVIDERS = new Set<string>(getProviders());\n\nexport function isApiKeyLoginProvider(\n\tproviderId: string,\n\toauthProviderIds: ReadonlySet<string>,\n\tbuiltInProviderIds: ReadonlySet<string> = BUILT_IN_MODEL_PROVIDERS,\n): boolean {\n\tif (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {\n\t\treturn true;\n\t}\n\tif (builtInProviderIds.has(providerId)) {\n\t\treturn false;\n\t}\n\treturn !oauthProviderIds.has(providerId);\n}\n\nfunction isUnknownModel(model: Model<any> | undefined): boolean {\n\treturn !!model && model.provider === \"unknown\" && model.id === \"unknown\" && model.api === \"unknown\";\n}\n\nfunction hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider {\n\treturn providerId in defaultModelPerProvider;\n}\n\n/** The slice of the interactive mode the login flows need. */\nexport interface LoginControllerDeps {\n\tui: TUI;\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\t/** The prompt editor (read at call time; the editor can be swapped). */\n\tgetEditor(): Component;\n\t/** Container that hosts the prompt editor and login dialogs. */\n\teditorContainer: Container;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n\t/** Recompute the footer's available provider count. */\n\tupdateAvailableProviderCount(): Promise<void>;\n\tupdateEditorBorderColor(): void;\n\tinvalidateFooter(): void;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n}\n\nexport class LoginController {\n\tconstructor(private readonly deps: LoginControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\tprivate restoreEditor(): void {\n\t\tconst { deps } = this;\n\t\tdeps.editorContainer.clear();\n\t\tdeps.editorContainer.addChild(deps.getEditor());\n\t\tdeps.ui.setFocus(deps.getEditor());\n\t\tdeps.ui.requestRender();\n\t}\n\n\tprivate getLoginProviderOptions(authType?: \"oauth\" | \"api_key\"): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst oauthProviders = authStorage.getOAuthProviders();\n\t\tconst oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));\n\t\tconst options: AuthSelectorProvider[] = oauthProviders.map((provider) => ({\n\t\t\tid: provider.id,\n\t\t\tname: provider.name,\n\t\t\tauthType: \"oauth\",\n\t\t}));\n\n\t\tconst modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));\n\t\tfor (const providerId of modelProviders) {\n\t\t\tif (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: \"api_key\",\n\t\t\t});\n\t\t}\n\n\t\tconst filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;\n\t\treturn filteredOptions.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate getLogoutProviderOptions(): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst options: AuthSelectorProvider[] = [];\n\n\t\tfor (const providerId of authStorage.list()) {\n\t\t\tconst credential = authStorage.get(providerId);\n\t\t\tif (!credential) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: credential.type,\n\t\t\t});\n\t\t}\n\n\t\treturn options.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate showLoginAuthTypeSelector(): void {\n\t\tconst subscriptionLabel = \"Use a subscription\";\n\t\tconst apiKeyLabel = \"Use an API key\";\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\t\"Select authentication method:\",\n\t\t\t\t[subscriptionLabel, apiKeyLabel],\n\t\t\t\t(option) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tconst authType = option === subscriptionLabel ? \"oauth\" : \"api_key\";\n\t\t\t\t\tthis.showLoginProviderSelector(authType);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate showLoginProviderSelector(authType: \"oauth\" | \"api_key\"): void {\n\t\tconst providerOptions = this.getLoginProviderOptions(authType);\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\tauthType === \"oauth\" ? \"No subscription providers available.\" : \"No API key providers available.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\t\"login\",\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (providerOption.authType === \"oauth\") {\n\t\t\t\t\t\tawait this.showLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.showApiKeyLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\t\t},\n\t\t\t\t(providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId),\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tasync showOAuthSelector(mode: \"login\" | \"logout\"): Promise<void> {\n\t\tif (mode === \"login\") {\n\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst providerOptions = this.getLogoutProviderOptions();\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\t\"No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\tmode,\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.session.modelRegistry.authStorage.logout(providerOption.id);\n\t\t\t\t\t\tthis.session.modelRegistry.refresh();\n\t\t\t\t\t\tawait this.deps.updateAvailableProviderCount();\n\t\t\t\t\t\tconst message =\n\t\t\t\t\t\t\tproviderOption.authType === \"oauth\"\n\t\t\t\t\t\t\t\t? `Logged out of ${providerOption.name}`\n\t\t\t\t\t\t\t\t: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;\n\t\t\t\t\t\tthis.deps.showStatus(message);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tthis.deps.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate async completeProviderAuthentication(\n\t\tproviderId: string,\n\t\tproviderName: string,\n\t\tauthType: \"oauth\" | \"api_key\",\n\t\tpreviousModel: Model<any> | undefined,\n\t): Promise<void> {\n\t\tthis.session.modelRegistry.refresh();\n\n\t\tconst actionLabel = authType === \"oauth\" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;\n\n\t\tlet selectedModel: Model<any> | undefined;\n\t\tlet selectionError: string | undefined;\n\t\tif (isUnknownModel(previousModel)) {\n\t\t\tconst availableModels = this.session.modelRegistry.getAvailable();\n\t\t\tconst providerModels = availableModels.filter((model) => model.provider === providerId);\n\t\t\tif (!hasDefaultModelProvider(providerId)) {\n\t\t\t\tselectionError = `${actionLabel}, but no default model is configured for provider \"${providerId}\". Use /model to select a model.`;\n\t\t\t} else if (providerModels.length === 0) {\n\t\t\t\tselectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;\n\t\t\t} else {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[providerId];\n\t\t\t\tselectedModel = providerModels.find((model) => model.id === defaultModelId);\n\t\t\t\tif (!selectedModel) {\n\t\t\t\t\tselectionError = `${actionLabel}, but its default model \"${defaultModelId}\" is not available. Use /model to select a model.`;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.session.setModel(selectedModel);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tselectedModel = undefined;\n\t\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tselectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.deps.updateAvailableProviderCount();\n\t\tthis.deps.invalidateFooter();\n\t\tthis.deps.updateEditorBorderColor();\n\t\tif (selectedModel) {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);\n\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);\n\t\t} else {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);\n\t\t\tif (selectionError) {\n\t\t\t\tthis.deps.showError(selectionError);\n\t\t\t} else {\n\t\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async showApiKeyLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst previousModel = this.session.model;\n\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\ttry {\n\t\t\tconst apiKey = (await dialog.showPrompt(\"Enter API key:\")).trim();\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"API key cannot be empty.\");\n\t\t\t}\n\n\t\t\tthis.session.modelRegistry.authStorage.set(providerId, { type: \"api_key\", key: apiKey });\n\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"api_key\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showOAuthLoginSelect(dialog: LoginDialogComponent, prompt: OAuthSelectPrompt): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst restoreDialog = () => {\n\t\t\t\tthis.deps.editorContainer.clear();\n\t\t\t\tthis.deps.editorContainer.addChild(dialog);\n\t\t\t\tthis.deps.ui.setFocus(dialog);\n\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t};\n\t\t\tconst labels = prompt.options.map((option) => option.label);\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\tprompt.message,\n\t\t\t\tlabels,\n\t\t\t\t(optionLabel) => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(prompt.options.find((option) => option.label === optionLabel)?.id);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.deps.editorContainer.clear();\n\t\t\tthis.deps.editorContainer.addChild(selector);\n\t\t\tthis.deps.ui.setFocus(selector);\n\t\t\tthis.deps.ui.requestRender();\n\t\t});\n\t}\n\n\tprivate async showLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst providerInfo = this.session.modelRegistry.authStorage\n\t\t\t.getOAuthProviders()\n\t\t\t.find((provider) => provider.id === providerId);\n\t\tconst previousModel = this.session.model;\n\n\t\t// Providers that use callback servers (can paste redirect URL)\n\t\tconst usesCallbackServer = providerInfo?.usesCallbackServer ?? false;\n\n\t\t// Create login dialog component\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\t// Show dialog in editor container\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\t// Promise for manual code input (racing with callback server)\n\t\tlet manualCodeResolve: ((code: string) => void) | undefined;\n\t\tlet manualCodeReject: ((err: Error) => void) | undefined;\n\t\tconst manualCodePromise = new Promise<string>((resolve, reject) => {\n\t\t\tmanualCodeResolve = resolve;\n\t\t\tmanualCodeReject = reject;\n\t\t});\n\n\t\ttry {\n\t\t\tawait this.session.modelRegistry.authStorage.login(providerId as OAuthProviderId, {\n\t\t\t\tonAuth: (info: { url: string; instructions?: string }) => {\n\t\t\t\t\tdialog.showAuth(info.url, info.instructions);\n\n\t\t\t\t\tif (usesCallbackServer) {\n\t\t\t\t\t\t// Show input for manual paste, racing with callback\n\t\t\t\t\t\tdialog\n\t\t\t\t\t\t\t.showManualInput(\"Paste redirect URL below, or complete login in browser:\")\n\t\t\t\t\t\t\t.then((value) => {\n\t\t\t\t\t\t\t\tif (value && manualCodeResolve) {\n\t\t\t\t\t\t\t\t\tmanualCodeResolve(value);\n\t\t\t\t\t\t\t\t\tmanualCodeResolve = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t\tif (manualCodeReject) {\n\t\t\t\t\t\t\t\t\tmanualCodeReject(new Error(\"Login cancelled\"));\n\t\t\t\t\t\t\t\t\tmanualCodeReject = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t} else if (providerId === \"github-copilot\") {\n\t\t\t\t\t\t// GitHub Copilot polls after onAuth\n\t\t\t\t\t\tdialog.showWaiting(\"Waiting for browser authentication...\");\n\t\t\t\t\t}\n\t\t\t\t\t// For Anthropic: onPrompt is called immediately after\n\t\t\t\t},\n\n\t\t\t\tonPrompt: async (prompt: { message: string; placeholder?: string }) => {\n\t\t\t\t\treturn dialog.showPrompt(prompt.message, prompt.placeholder);\n\t\t\t\t},\n\n\t\t\t\tonProgress: (message: string) => {\n\t\t\t\t\tdialog.showProgress(message);\n\t\t\t\t},\n\n\t\t\t\tonSelect: (prompt: OAuthSelectPrompt) => this.showOAuthLoginSelect(dialog, prompt),\n\n\t\t\t\tonManualCodeInput: () => manualCodePromise,\n\n\t\t\t\tsignal: dialog.signal,\n\t\t\t});\n\n\t\t\t// Success\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"oauth\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to login to ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"login-controller.js","sourceRoot":"","sources":["../../../src/modes/interactive/login-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Auth / login flows for the interactive mode (/login, /logout).\n *\n * Owns the provider auth-type selector, the OAuth and API-key login dialogs,\n * and post-login model selection. Extracted from\n * interactive-mode.ts behind a narrow LoginControllerDeps interface; the\n * `/login` and `/logout` commands call `showOAuthSelector`.\n */\n\nimport { getProviders, type Model, type OAuthProviderId, type OAuthSelectPrompt } from \"@kolisachint/hoocode-ai\";\nimport type { Component, Container, TUI } from \"@kolisachint/hoocode-tui\";\nimport { getAuthPath } from \"../../config.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport { defaultModelPerProvider } from \"../../core/model-resolver.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"../../core/provider-display-names.js\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.js\";\nimport { LoginDialogComponent } from \"./components/login-dialog.js\";\nimport { type AuthSelectorProvider, OAuthSelectorComponent } from \"./components/oauth-selector.js\";\n\nconst BUILT_IN_MODEL_PROVIDERS = new Set<string>(getProviders());\n\nexport function isApiKeyLoginProvider(\n\tproviderId: string,\n\toauthProviderIds: ReadonlySet<string>,\n\tbuiltInProviderIds: ReadonlySet<string> = BUILT_IN_MODEL_PROVIDERS,\n): boolean {\n\tif (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {\n\t\treturn true;\n\t}\n\tif (builtInProviderIds.has(providerId)) {\n\t\treturn false;\n\t}\n\treturn !oauthProviderIds.has(providerId);\n}\n\nfunction isUnknownModel(model: Model<any> | undefined): boolean {\n\treturn !!model && model.provider === \"unknown\" && model.id === \"unknown\" && model.api === \"unknown\";\n}\n\nfunction hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider {\n\treturn providerId in defaultModelPerProvider;\n}\n\n/** The slice of the interactive mode the login flows need. */\nexport interface LoginControllerDeps {\n\tui: TUI;\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\t/** The prompt editor (read at call time; the editor can be swapped). */\n\tgetEditor(): Component;\n\t/** Container that hosts the prompt editor and login dialogs. */\n\teditorContainer: Container;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n\t/** Recompute the footer's available provider count. */\n\tupdateAvailableProviderCount(): Promise<void>;\n\tupdateEditorBorderColor(): void;\n\tinvalidateFooter(): void;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n}\n\nexport class LoginController {\n\tconstructor(private readonly deps: LoginControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\tprivate restoreEditor(): void {\n\t\tconst { deps } = this;\n\t\tdeps.editorContainer.clear();\n\t\tdeps.editorContainer.addChild(deps.getEditor());\n\t\tdeps.ui.setFocus(deps.getEditor());\n\t\tdeps.ui.requestRender();\n\t}\n\n\tprivate getLoginProviderOptions(authType?: \"oauth\" | \"api_key\"): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst oauthProviders = authStorage.getOAuthProviders();\n\t\tconst oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));\n\t\tconst options: AuthSelectorProvider[] = oauthProviders.map((provider) => ({\n\t\t\tid: provider.id,\n\t\t\tname: provider.name,\n\t\t\tauthType: \"oauth\",\n\t\t}));\n\n\t\tconst modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));\n\t\tfor (const providerId of modelProviders) {\n\t\t\tif (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: \"api_key\",\n\t\t\t});\n\t\t}\n\n\t\tconst filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;\n\t\treturn filteredOptions.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate getLogoutProviderOptions(): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst options: AuthSelectorProvider[] = [];\n\n\t\tfor (const providerId of authStorage.list()) {\n\t\t\tconst credential = authStorage.get(providerId);\n\t\t\tif (!credential) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: credential.type,\n\t\t\t});\n\t\t}\n\n\t\treturn options.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate showLoginAuthTypeSelector(): void {\n\t\tconst subscriptionLabel = \"Use a subscription\";\n\t\tconst apiKeyLabel = \"Use an API key\";\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\t\"Select authentication method:\",\n\t\t\t\t[subscriptionLabel, apiKeyLabel],\n\t\t\t\t(option) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tconst authType = option === subscriptionLabel ? \"oauth\" : \"api_key\";\n\t\t\t\t\tthis.showLoginProviderSelector(authType);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate showLoginProviderSelector(authType: \"oauth\" | \"api_key\"): void {\n\t\tconst providerOptions = this.getLoginProviderOptions(authType);\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\tauthType === \"oauth\" ? \"No subscription providers available.\" : \"No API key providers available.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\t\"login\",\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (providerOption.authType === \"oauth\") {\n\t\t\t\t\t\tawait this.showLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.showApiKeyLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\t\t},\n\t\t\t\t(providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId),\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tasync showOAuthSelector(mode: \"login\" | \"logout\"): Promise<void> {\n\t\tif (mode === \"login\") {\n\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst providerOptions = this.getLogoutProviderOptions();\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\t\"No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\tmode,\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.session.modelRegistry.authStorage.logout(providerOption.id);\n\t\t\t\t\t\tthis.session.modelRegistry.refresh();\n\t\t\t\t\t\tawait this.deps.updateAvailableProviderCount();\n\t\t\t\t\t\tconst message =\n\t\t\t\t\t\t\tproviderOption.authType === \"oauth\"\n\t\t\t\t\t\t\t\t? `Logged out of ${providerOption.name}`\n\t\t\t\t\t\t\t\t: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;\n\t\t\t\t\t\tthis.deps.showStatus(message);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tthis.deps.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate async completeProviderAuthentication(\n\t\tproviderId: string,\n\t\tproviderName: string,\n\t\tauthType: \"oauth\" | \"api_key\",\n\t\tpreviousModel: Model<any> | undefined,\n\t): Promise<void> {\n\t\tthis.session.modelRegistry.refresh();\n\n\t\tconst actionLabel = authType === \"oauth\" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;\n\n\t\tlet selectedModel: Model<any> | undefined;\n\t\tlet selectionError: string | undefined;\n\t\tif (isUnknownModel(previousModel)) {\n\t\t\tconst availableModels = this.session.modelRegistry.getAvailable();\n\t\t\tconst providerModels = availableModels.filter((model) => model.provider === providerId);\n\t\t\tif (!hasDefaultModelProvider(providerId)) {\n\t\t\t\tselectionError = `${actionLabel}, but no default model is configured for provider \"${providerId}\". Use /model to select a model.`;\n\t\t\t} else if (providerModels.length === 0) {\n\t\t\t\tselectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;\n\t\t\t} else {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[providerId];\n\t\t\t\tselectedModel = providerModels.find((model) => model.id === defaultModelId);\n\t\t\t\tif (!selectedModel) {\n\t\t\t\t\tselectionError = `${actionLabel}, but its default model \"${defaultModelId}\" is not available. Use /model to select a model.`;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.session.setModel(selectedModel);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tselectedModel = undefined;\n\t\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tselectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.deps.updateAvailableProviderCount();\n\t\tthis.deps.invalidateFooter();\n\t\tthis.deps.updateEditorBorderColor();\n\t\tif (selectedModel) {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);\n\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);\n\t\t} else {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);\n\t\t\tif (selectionError) {\n\t\t\t\tthis.deps.showError(selectionError);\n\t\t\t} else {\n\t\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async showApiKeyLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst previousModel = this.session.model;\n\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\ttry {\n\t\t\tconst apiKey = (await dialog.showPrompt(\"Enter API key:\")).trim();\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"API key cannot be empty.\");\n\t\t\t}\n\n\t\t\tthis.session.modelRegistry.authStorage.set(providerId, { type: \"api_key\", key: apiKey });\n\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"api_key\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showOAuthLoginSelect(dialog: LoginDialogComponent, prompt: OAuthSelectPrompt): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst restoreDialog = () => {\n\t\t\t\tthis.deps.editorContainer.clear();\n\t\t\t\tthis.deps.editorContainer.addChild(dialog);\n\t\t\t\tthis.deps.ui.setFocus(dialog);\n\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t};\n\t\t\tconst labels = prompt.options.map((option) => option.label);\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\tprompt.message,\n\t\t\t\tlabels,\n\t\t\t\t(optionLabel) => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(prompt.options.find((option) => option.label === optionLabel)?.id);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.deps.editorContainer.clear();\n\t\t\tthis.deps.editorContainer.addChild(selector);\n\t\t\tthis.deps.ui.setFocus(selector);\n\t\t\tthis.deps.ui.requestRender();\n\t\t});\n\t}\n\n\tprivate async showLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst providerInfo = this.session.modelRegistry.authStorage\n\t\t\t.getOAuthProviders()\n\t\t\t.find((provider) => provider.id === providerId);\n\t\tconst previousModel = this.session.model;\n\n\t\t// Providers that use callback servers (can paste redirect URL)\n\t\tconst usesCallbackServer = providerInfo?.usesCallbackServer ?? false;\n\n\t\t// Create login dialog component\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\t// Show dialog in editor container\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\t// Promise for manual code input (racing with callback server)\n\t\tlet manualCodeResolve: ((code: string) => void) | undefined;\n\t\tlet manualCodeReject: ((err: Error) => void) | undefined;\n\t\tconst manualCodePromise = new Promise<string>((resolve, reject) => {\n\t\t\tmanualCodeResolve = resolve;\n\t\t\tmanualCodeReject = reject;\n\t\t});\n\n\t\ttry {\n\t\t\tawait this.session.modelRegistry.authStorage.login(providerId as OAuthProviderId, {\n\t\t\t\tonAuth: (info: { url: string; instructions?: string }) => {\n\t\t\t\t\tdialog.showAuth(info.url, info.instructions);\n\n\t\t\t\t\tif (usesCallbackServer) {\n\t\t\t\t\t\t// Show input for manual paste, racing with callback\n\t\t\t\t\t\tdialog\n\t\t\t\t\t\t\t.showManualInput(\"Paste redirect URL below, or complete login in browser:\")\n\t\t\t\t\t\t\t.then((value) => {\n\t\t\t\t\t\t\t\tif (value && manualCodeResolve) {\n\t\t\t\t\t\t\t\t\tmanualCodeResolve(value);\n\t\t\t\t\t\t\t\t\tmanualCodeResolve = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t\tif (manualCodeReject) {\n\t\t\t\t\t\t\t\t\tmanualCodeReject(new Error(\"Login cancelled\"));\n\t\t\t\t\t\t\t\t\tmanualCodeReject = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t} else if (providerId === \"github-copilot\") {\n\t\t\t\t\t\t// GitHub Copilot polls after onAuth\n\t\t\t\t\t\tdialog.showWaiting(\"Waiting for browser authentication...\");\n\t\t\t\t\t}\n\t\t\t\t\t// For Anthropic: onPrompt is called immediately after\n\t\t\t\t},\n\n\t\t\t\tonPrompt: async (prompt: { message: string; placeholder?: string }) => {\n\t\t\t\t\treturn dialog.showPrompt(prompt.message, prompt.placeholder);\n\t\t\t\t},\n\n\t\t\t\tonProgress: (message: string) => {\n\t\t\t\t\tdialog.showProgress(message);\n\t\t\t\t},\n\n\t\t\t\tonSelect: (prompt: OAuthSelectPrompt) => this.showOAuthLoginSelect(dialog, prompt),\n\n\t\t\t\tonManualCodeInput: () => manualCodePromise,\n\n\t\t\t\tsignal: dialog.signal,\n\t\t\t});\n\n\t\t\t// Success\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"oauth\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to login to ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -6,7 +6,6 @@
6
6
  */
7
7
  import { type Container, Text } from "@kolisachint/hoocode-tui";
8
8
  import type { AgentDefinition } from "../../core/agent-frontmatter.js";
9
- import type { ContextFile } from "../../core/context-files.js";
10
9
  import type { ExtensionRunner } from "../../core/extensions/index.js";
11
10
  import type { PromptTemplate } from "../../core/prompt-templates.js";
12
11
  import type { ResourceDiagnostic, ResourceLoader } from "../../core/resource-loader.js";
@@ -22,25 +21,6 @@ export declare class ExpandableText extends Text implements Expandable {
22
21
  setExpanded(expanded: boolean): void;
23
22
  }
24
23
  export declare function formatDisplayPath(p: string): string;
25
- export declare function formatExtensionDisplayPath(extensionPath: string): string;
26
- export declare function formatContextPath(p: string, cwd: string): string;
27
- /**
28
- * Inline cost note for a context file: these are injected into the system prompt
29
- * on every turn, so an oversized one is priced where it is listed instead of in
30
- * a separate warning line.
31
- */
32
- export declare function contextSizeNote(file: ContextFile): string;
33
- /**
34
- * Get a short path relative to the package root for display.
35
- */
36
- export declare function getShortPath(fullPath: string, sourceInfo?: SourceInfo): string;
37
- export declare function getCompactPathLabel(resourcePath: string, sourceInfo?: SourceInfo): string;
38
- export declare function getCompactExtensionLabels(extensions: Array<{
39
- path: string;
40
- sourceInfo?: SourceInfo;
41
- displayName?: string;
42
- }>): string[];
43
- export declare function formatDiagnostics(diagnostics: readonly ResourceDiagnostic[], sourceInfos: Map<string, SourceInfo>): string;
44
24
  /** The slice of the interactive mode the resource listing renders into. */
45
25
  export interface ResourceDisplayDeps {
46
26
  chatContainer: Container;
@@ -1 +1 @@
1
- {"version":3,"file":"resource-display.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/resource-display.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Startup/reload resource listing: path and source-label formatting, scope\n * grouping, diagnostics rendering, and the [Resources]/[Skills]/… sections\n * shown in the chat. Extracted from interactive-mode.ts; everything here is\n * either pure formatting or rendering into a container passed via deps.\n */\n\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { type Container, Spacer, Text, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentDefinition } from \"../../core/agent-frontmatter.js\";\nimport { summarizeAgentDescription } from \"../../core/agent-registry.js\";\nimport type { ContextFile } from \"../../core/context-files.js\";\nimport type { ExtensionRunner } from \"../../core/extensions/index.js\";\nimport { formatTokens } from \"../../core/format-tokens.js\";\nimport { getMcpServerStatuses } from \"../../core/mcp-status.js\";\nimport type { PromptTemplate } from \"../../core/prompt-templates.js\";\nimport type { ResourceDiagnostic, ResourceLoader } from \"../../core/resource-loader.js\";\nimport type { SourceInfo } from \"../../core/source-info.js\";\nimport { parseGitUrl } from \"../../utils/git.js\";\nimport { getCwdRelativePath } from \"../../utils/paths.js\";\nimport { CATEGORY_GLYPH, type CategoryKey, SEGMENT_SEP } from \"./brand.js\";\nimport { appKeyLabel } from \"./components/keybinding-hints.js\";\nimport { type ThemeColor, theme } from \"./theme/theme.js\";\n\n// ============================================================================\n// Expandable sections\n// ============================================================================\n\nexport interface Expandable {\n\tsetExpanded(expanded: boolean): void;\n}\n\n/**\n * Left rail for the startup summary. Lines up under the banner's owl glyph so\n * the whole first page reads as one column instead of three stray indents.\n */\nconst RAIL = \" \";\n\nexport function isExpandable(obj: unknown): obj is Expandable {\n\treturn typeof obj === \"object\" && obj !== null && \"setExpanded\" in obj;\n}\n\nexport class ExpandableText extends Text implements Expandable {\n\tconstructor(\n\t\tprivate collapsed: () => string,\n\t\tprivate expanded: () => string,\n\t\tinitiallyExpanded: boolean,\n\t\tpaddingX: number,\n\t\tpaddingY: number,\n\t) {\n\t\tsuper(initiallyExpanded ? expanded() : collapsed(), paddingX, paddingY);\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.setText(expanded ? this.expanded() : this.collapsed());\n\t}\n}\n\n// ============================================================================\n// Path and source-label formatting\n// ============================================================================\n\nexport function formatDisplayPath(p: string): string {\n\tconst home = os.homedir();\n\tlet result = p;\n\n\t// Replace home directory with ~\n\tif (result.startsWith(home)) {\n\t\tresult = `~${result.slice(home.length)}`;\n\t}\n\n\treturn result;\n}\n\nexport function formatExtensionDisplayPath(extensionPath: string): string {\n\tlet result = formatDisplayPath(extensionPath);\n\tresult = result.replace(/\\/index\\.ts$/, \"\").replace(/\\/index\\.js$/, \"\");\n\treturn result;\n}\n\nexport function formatContextPath(p: string, cwd: string): string {\n\tconst resolvedCwd = path.resolve(cwd);\n\tconst absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(resolvedCwd, p);\n\tconst relativePath = getCwdRelativePath(absolutePath, resolvedCwd);\n\tif (relativePath !== undefined) {\n\t\treturn relativePath;\n\t}\n\n\treturn formatDisplayPath(absolutePath);\n}\n\n/**\n * Inline cost note for a context file: these are injected into the system prompt\n * on every turn, so an oversized one is priced where it is listed instead of in\n * a separate warning line.\n */\nexport function contextSizeNote(file: ContextFile): string {\n\tif (!file.size) return \"\";\n\tconst advice = file.size === \"truncated\" ? \"truncated\" : \"consider trimming\";\n\tconst tokens = file.tokens === undefined ? undefined : `~${formatTokens(file.tokens)} tokens`;\n\treturn theme.fg(\"warning\", ` ${tokens ? `${tokens} ${SEGMENT_SEP} ` : \"\"}${advice}`);\n}\n\n/**\n * Get a short path relative to the package root for display.\n */\nexport function getShortPath(fullPath: string, sourceInfo?: SourceInfo): string {\n\tconst baseDir = sourceInfo?.baseDir;\n\tif (baseDir && isPackageSource(sourceInfo)) {\n\t\tconst relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));\n\t\tif (\n\t\t\trelativePath &&\n\t\t\trelativePath !== \".\" &&\n\t\t\t!relativePath.startsWith(\"..\") &&\n\t\t\t!relativePath.startsWith(`..${path.sep}`) &&\n\t\t\t!path.isAbsolute(relativePath)\n\t\t) {\n\t\t\treturn relativePath.replace(/\\\\/g, \"/\");\n\t\t}\n\t}\n\n\tconst source = sourceInfo?.source ?? \"\";\n\tconst npmMatch = fullPath.match(/node_modules\\/(@?[^/]+(?:\\/[^/]+)?)\\/(.*)/);\n\tif (npmMatch && source.startsWith(\"npm:\")) {\n\t\treturn npmMatch[2];\n\t}\n\n\tconst gitMatch = fullPath.match(/git\\/[^/]+\\/[^/]+\\/(.*)/);\n\tif (gitMatch && source.startsWith(\"git:\")) {\n\t\treturn gitMatch[1];\n\t}\n\n\treturn formatDisplayPath(fullPath);\n}\n\nexport function getCompactPathLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\tconst shortPath = getShortPath(resourcePath, sourceInfo);\n\tconst normalizedPath = shortPath.replace(/\\\\/g, \"/\");\n\tconst segments = normalizedPath.split(\"/\").filter((segment) => segment.length > 0 && segment !== \"~\");\n\tif (segments.length > 0) {\n\t\treturn segments[segments.length - 1]!;\n\t}\n\treturn shortPath;\n}\n\nfunction getCompactPackageSourceLabel(sourceInfo?: SourceInfo): string {\n\tconst source = sourceInfo?.source ?? \"\";\n\tif (source.startsWith(\"npm:\")) {\n\t\treturn source.slice(\"npm:\".length) || source;\n\t}\n\n\tconst gitSource = parseGitUrl(source);\n\tif (gitSource) {\n\t\treturn gitSource.path || source;\n\t}\n\n\treturn source;\n}\n\nfunction getCompactExtensionLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\tif (!isPackageSource(sourceInfo)) {\n\t\treturn getCompactPathLabel(resourcePath, sourceInfo);\n\t}\n\n\tconst sourceLabel = getCompactPackageSourceLabel(sourceInfo);\n\tif (!sourceLabel) {\n\t\treturn getCompactPathLabel(resourcePath, sourceInfo);\n\t}\n\n\tconst shortPath = getShortPath(resourcePath, sourceInfo).replace(/\\\\/g, \"/\");\n\tconst packagePath = shortPath.startsWith(\"extensions/\") ? shortPath.slice(\"extensions/\".length) : shortPath;\n\tconst parsedPath = path.posix.parse(packagePath);\n\n\tif (parsedPath.name === \"index\") {\n\t\treturn !parsedPath.dir || parsedPath.dir === \".\" ? sourceLabel : `${sourceLabel}:${parsedPath.dir}`;\n\t}\n\n\treturn `${sourceLabel}:${packagePath}`;\n}\n\nfunction getCompactDisplayPathSegments(resourcePath: string): string[] {\n\treturn formatDisplayPath(resourcePath)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.split(\"/\")\n\t\t.filter((segment) => segment.length > 0 && segment !== \"~\");\n}\n\nfunction getCompactNonPackageExtensionLabel(\n\tresourcePath: string,\n\tindex: number,\n\tallPaths: Array<{ path: string; segments: string[] }>,\n): string {\n\tconst segments = allPaths[index]?.segments;\n\tif (!segments || segments.length === 0) {\n\t\treturn getCompactPathLabel(resourcePath);\n\t}\n\n\tfor (let segmentCount = 1; segmentCount <= segments.length; segmentCount += 1) {\n\t\tconst candidate = segments.slice(-segmentCount).join(\"/\");\n\t\tconst isUnique = allPaths.every((item, itemIndex) => {\n\t\t\tif (itemIndex === index) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn item.segments.slice(-segmentCount).join(\"/\") !== candidate;\n\t\t});\n\n\t\tif (isUnique) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\treturn segments.join(\"/\");\n}\n\nexport function getCompactExtensionLabels(\n\textensions: Array<{ path: string; sourceInfo?: SourceInfo; displayName?: string }>,\n): string[] {\n\tconst nonPackageExtensions = extensions\n\t\t.map((extension) => {\n\t\t\tconst segments = getCompactDisplayPathSegments(extension.path);\n\t\t\tconst lastSegment = segments[segments.length - 1];\n\t\t\tif (segments.length > 1 && (lastSegment === \"index.ts\" || lastSegment === \"index.js\")) {\n\t\t\t\tsegments.pop();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tpath: extension.path,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t\tsegments,\n\t\t\t};\n\t\t})\n\t\t.filter((extension) => !isPackageSource(extension.sourceInfo));\n\n\treturn extensions.map((extension) => {\n\t\tif (extension.displayName) {\n\t\t\treturn extension.displayName;\n\t\t}\n\n\t\tif (isPackageSource(extension.sourceInfo)) {\n\t\t\treturn getCompactExtensionLabel(extension.path, extension.sourceInfo);\n\t\t}\n\n\t\tconst nonPackageIndex = nonPackageExtensions.findIndex((item) => item.path === extension.path);\n\t\tif (nonPackageIndex === -1) {\n\t\t\treturn getCompactPathLabel(extension.path, extension.sourceInfo);\n\t\t}\n\n\t\treturn getCompactNonPackageExtensionLabel(extension.path, nonPackageIndex, nonPackageExtensions);\n\t});\n}\n\nfunction getDisplaySourceInfo(sourceInfo?: SourceInfo): {\n\tlabel: string;\n\tscopeLabel?: string;\n\tcolor: \"accent\" | \"muted\";\n} {\n\tconst source = sourceInfo?.source ?? \"local\";\n\tconst scope = sourceInfo?.scope ?? \"project\";\n\tif (source === \"local\") {\n\t\tif (scope === \"user\") {\n\t\t\treturn { label: \"user\", color: \"muted\" };\n\t\t}\n\t\tif (scope === \"project\") {\n\t\t\treturn { label: \"project\", color: \"muted\" };\n\t\t}\n\t\tif (scope === \"temporary\") {\n\t\t\treturn { label: \"path\", scopeLabel: \"temp\", color: \"muted\" };\n\t\t}\n\t\treturn { label: \"path\", color: \"muted\" };\n\t}\n\n\tif (source === \"cli\") {\n\t\treturn { label: \"path\", scopeLabel: scope === \"temporary\" ? \"temp\" : undefined, color: \"muted\" };\n\t}\n\n\tconst scopeLabel =\n\t\tscope === \"user\" ? \"user\" : scope === \"project\" ? \"project\" : scope === \"temporary\" ? \"temp\" : undefined;\n\treturn { label: source, scopeLabel, color: \"accent\" };\n}\n\nfunction getScopeGroup(sourceInfo?: SourceInfo): \"user\" | \"project\" | \"path\" {\n\tconst source = sourceInfo?.source ?? \"local\";\n\tconst scope = sourceInfo?.scope ?? \"project\";\n\tif (source === \"cli\" || scope === \"temporary\") return \"path\";\n\tif (scope === \"user\") return \"user\";\n\tif (scope === \"project\") return \"project\";\n\treturn \"path\";\n}\n\nfunction isPackageSource(sourceInfo?: SourceInfo): boolean {\n\tconst source = sourceInfo?.source ?? \"\";\n\treturn source.startsWith(\"npm:\") || source.startsWith(\"git:\");\n}\n\n// ============================================================================\n// Scope grouping and diagnostics\n// ============================================================================\n\ninterface ScopedItem {\n\tpath: string;\n\tsourceInfo?: SourceInfo;\n\tdisplayName?: string;\n}\n\ninterface ScopeGroup {\n\tscope: \"user\" | \"project\" | \"path\";\n\tpaths: ScopedItem[];\n\tpackages: Map<string, ScopedItem[]>;\n}\n\nfunction buildScopeGroups(items: ScopedItem[]): ScopeGroup[] {\n\tconst groups: Record<\"user\" | \"project\" | \"path\", ScopeGroup> = {\n\t\tuser: { scope: \"user\", paths: [], packages: new Map() },\n\t\tproject: { scope: \"project\", paths: [], packages: new Map() },\n\t\tpath: { scope: \"path\", paths: [], packages: new Map() },\n\t};\n\n\tfor (const item of items) {\n\t\tconst groupKey = getScopeGroup(item.sourceInfo);\n\t\tconst group = groups[groupKey];\n\t\tconst source = item.sourceInfo?.source ?? \"local\";\n\n\t\tif (isPackageSource(item.sourceInfo)) {\n\t\t\tconst list = group.packages.get(source) ?? [];\n\t\t\tlist.push(item);\n\t\t\tgroup.packages.set(source, list);\n\t\t} else {\n\t\t\tgroup.paths.push(item);\n\t\t}\n\t}\n\n\treturn [groups.project, groups.user, groups.path].filter(\n\t\t(group) => group.paths.length > 0 || group.packages.size > 0,\n\t);\n}\n\nfunction formatScopeGroups(\n\tgroups: ScopeGroup[],\n\toptions: {\n\t\tformatPath: (item: ScopedItem) => string;\n\t\tformatPackagePath: (item: ScopedItem, source: string) => string;\n\t},\n): string {\n\tconst lines: string[] = [];\n\n\tfor (const group of groups) {\n\t\tlines.push(` ${theme.fg(\"accent\", group.scope)}`);\n\n\t\tconst sortedPaths = [...group.paths].sort((a, b) => a.path.localeCompare(b.path));\n\t\tfor (const item of sortedPaths) {\n\t\t\tlines.push(theme.fg(\"dim\", ` ${options.formatPath(item)}`));\n\t\t}\n\n\t\tconst sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));\n\t\tfor (const [source, items] of sortedPackages) {\n\t\t\tlines.push(` ${theme.fg(\"mdLink\", source)}`);\n\t\t\tconst sortedPackagePaths = [...items].sort((a, b) => a.path.localeCompare(b.path));\n\t\t\tfor (const item of sortedPackagePaths) {\n\t\t\t\tlines.push(theme.fg(\"dim\", ` ${options.formatPackagePath(item, source)}`));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction findSourceInfoForPath(p: string, sourceInfos: Map<string, SourceInfo>): SourceInfo | undefined {\n\tconst exact = sourceInfos.get(p);\n\tif (exact) return exact;\n\n\tlet current = p;\n\twhile (current.includes(\"/\")) {\n\t\tcurrent = current.substring(0, current.lastIndexOf(\"/\"));\n\t\tconst parent = sourceInfos.get(current);\n\t\tif (parent) return parent;\n\t}\n\n\treturn undefined;\n}\n\nfunction formatPathWithSource(p: string, sourceInfo?: SourceInfo): string {\n\tif (sourceInfo) {\n\t\tconst shortPath = getShortPath(p, sourceInfo);\n\t\tconst { label, scopeLabel } = getDisplaySourceInfo(sourceInfo);\n\t\tconst labelText = scopeLabel ? `${label} (${scopeLabel})` : label;\n\t\treturn `${labelText} ${shortPath}`;\n\t}\n\treturn formatDisplayPath(p);\n}\n\nexport function formatDiagnostics(\n\tdiagnostics: readonly ResourceDiagnostic[],\n\tsourceInfos: Map<string, SourceInfo>,\n): string {\n\tconst lines: string[] = [];\n\n\t// Group collision diagnostics by name\n\tconst collisions = new Map<string, ResourceDiagnostic[]>();\n\tconst otherDiagnostics: ResourceDiagnostic[] = [];\n\n\tfor (const d of diagnostics) {\n\t\tif (d.type === \"collision\" && d.collision) {\n\t\t\tconst list = collisions.get(d.collision.name) ?? [];\n\t\t\tlist.push(d);\n\t\t\tcollisions.set(d.collision.name, list);\n\t\t} else {\n\t\t\totherDiagnostics.push(d);\n\t\t}\n\t}\n\n\t// Format collision diagnostics grouped by name\n\tfor (const [name, collisionList] of collisions) {\n\t\tconst first = collisionList[0]?.collision;\n\t\tif (!first) continue;\n\t\tlines.push(theme.fg(\"warning\", ` \"${name}\" collision:`));\n\t\tlines.push(\n\t\t\ttheme.fg(\n\t\t\t\t\"dim\",\n\t\t\t\t` ${theme.fg(\"success\", \"✓\")} ${formatPathWithSource(first.winnerPath, findSourceInfoForPath(first.winnerPath, sourceInfos))}`,\n\t\t\t),\n\t\t);\n\t\tfor (const d of collisionList) {\n\t\t\tif (d.collision) {\n\t\t\t\tlines.push(\n\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\"dim\",\n\t\t\t\t\t\t` ${theme.fg(\"warning\", \"✗\")} ${formatPathWithSource(d.collision.loserPath, findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const d of otherDiagnostics) {\n\t\tif (d.path) {\n\t\t\tconst formattedPath = formatPathWithSource(d.path, findSourceInfoForPath(d.path, sourceInfos));\n\t\t\tlines.push(theme.fg(d.type === \"error\" ? \"error\" : \"warning\", ` ${formattedPath}`));\n\t\t\tlines.push(theme.fg(d.type === \"error\" ? \"error\" : \"warning\", ` ${d.message}`));\n\t\t} else {\n\t\t\tlines.push(theme.fg(d.type === \"error\" ? \"error\" : \"warning\", ` ${d.message}`));\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// ============================================================================\n// Loaded-resources listing\n// ============================================================================\n\n/** The slice of the interactive mode the resource listing renders into. */\nexport interface ResourceDisplayDeps {\n\tchatContainer: Container;\n\tgetCwd(): string;\n\tgetResourceLoader(): ResourceLoader;\n\tgetPromptTemplates(): ReadonlyArray<PromptTemplate>;\n\tgetExtensionRunner(): ExtensionRunner;\n\tgetActiveMode(): string;\n\tgetSubagentEnabled(): boolean;\n\t/** Dispatchable subagents in this cwd (0 when the Task tool is off). */\n\tgetAgentCount(): number;\n\t/** Dispatchable agents for this cwd (empty when Task tool is off). */\n\tgetAgents(): AgentDefinition[];\n\t/**\n\t * One-line session state (model, thinking level, auth caveats) shown under the\n\t * summary. Undefined when there is nothing worth stating.\n\t */\n\tgetStateLine?(): string | undefined;\n\tquietStartup(): boolean;\n\tverbose: boolean;\n\t/** Startup expansion state for the collapsible sections. */\n\tisExpanded(): boolean;\n\tgetBuiltInCommandConflictDiagnostics(extensionRunner: ExtensionRunner): ResourceDiagnostic[];\n}\n\n/**\n * Whether a `showLoadedResources` call renders the full summary rather than\n * diagnostics only. Exported so callers can skip preparing summary-only data\n * (and claiming once-per-session notices) on a quiet startup.\n */\nexport function willShowResourceListing(\n\tdeps: Pick<ResourceDisplayDeps, \"verbose\" | \"quietStartup\">,\n\toptions?: { force?: boolean },\n): boolean {\n\treturn options?.force === true || deps.verbose || !deps.quietStartup();\n}\n\nexport function showLoadedResources(\n\tdeps: ResourceDisplayDeps,\n\toptions?: {\n\t\textensions?: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\tforce?: boolean;\n\t\tshowDiagnosticsWhenQuiet?: boolean;\n\t},\n): void {\n\tconst showListing = willShowResourceListing(deps, options);\n\tconst showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;\n\tif (!showListing && !showDiagnostics) {\n\t\treturn;\n\t}\n\n\tconst resourceLoader = deps.getResourceLoader();\n\tconst chatContainer = deps.chatContainer;\n\n\tconst sectionHeader = (name: string, color: ThemeColor = \"mdHeading\") => theme.fg(color, `[${name}]`);\n\tconst formatCompactList = (items: string[], listOptions?: { sort?: boolean }): string => {\n\t\tconst labels = items.map((item) => item.trim()).filter((item) => item.length > 0);\n\t\tif (listOptions?.sort !== false) {\n\t\t\tlabels.sort((a, b) => a.localeCompare(b));\n\t\t}\n\t\treturn theme.fg(\"dim\", ` ${labels.join(\", \")}`);\n\t};\n\n\tconst skillsResult = resourceLoader.getSkills();\n\tconst promptsResult = resourceLoader.getPrompts();\n\tconst themesResult = resourceLoader.getThemes();\n\tconst extensions =\n\t\toptions?.extensions ??\n\t\tresourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.filter((extension) => !extension.internal)\n\t\t\t.map((extension) => ({\n\t\t\t\tpath: extension.path,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t\tdisplayName: extension.displayName,\n\t\t\t}));\n\tconst sourceInfos = new Map<string, SourceInfo>();\n\tfor (const extension of extensions) {\n\t\tif (extension.sourceInfo) {\n\t\t\tsourceInfos.set(extension.path, extension.sourceInfo);\n\t\t}\n\t}\n\tfor (const skill of skillsResult.skills) {\n\t\tif (skill.sourceInfo) {\n\t\t\tsourceInfos.set(skill.filePath, skill.sourceInfo);\n\t\t}\n\t}\n\tfor (const prompt of promptsResult.prompts) {\n\t\tif (prompt.sourceInfo) {\n\t\t\tsourceInfos.set(prompt.filePath, prompt.sourceInfo);\n\t\t}\n\t}\n\tfor (const loadedTheme of themesResult.themes) {\n\t\tif (loadedTheme.sourcePath && loadedTheme.sourceInfo) {\n\t\t\tsourceInfos.set(loadedTheme.sourcePath, loadedTheme.sourceInfo);\n\t\t}\n\t}\n\n\tif (showListing) {\n\t\tchatContainer.addChild(new Spacer(1));\n\n\t\tconst { agentsFiles: contextFiles, warnings: contextWarnings } = resourceLoader.getAgentsFiles();\n\t\tconst skills = skillsResult.skills;\n\t\tconst templates = deps.getPromptTemplates();\n\t\tconst loadedThemes = themesResult.themes;\n\t\tconst customThemes = loadedThemes.filter((t) => t.sourcePath);\n\t\tconst agentCount = deps.getAgentCount();\n\t\t// Live servers, whatever their source (mcp.json, per-server files, plugins) —\n\t\t// the loader records each connect outcome, so this is what the session can\n\t\t// actually call, not what was merely configured.\n\t\tconst mcpStatuses = getMcpServerStatuses();\n\t\t// Plugins register their synthetic extension factory as `plugin:<id>`;\n\t\t// split them out so plugins and code extensions each get their own count.\n\t\t// (displayName is absent from the options-provided extension shape, so read\n\t\t// it structurally.)\n\t\tconst displayNameOf = (e: object): string | undefined => {\n\t\t\tconst value = (e as { displayName?: unknown }).displayName;\n\t\t\treturn typeof value === \"string\" ? value : undefined;\n\t\t};\n\t\tconst pluginCount = extensions.filter((e) => displayNameOf(e)?.startsWith(\"plugin:\")).length;\n\t\tconst codeExtensionCount = extensions.length - pluginCount;\n\n\t\t// ── Counted capability grid ──────────────────────────────────────────────\n\t\t// One cell per loaded capability class (glyph + count + label), so \"what\n\t\t// can this session do\" reads at a glance; names/paths sit one keypress\n\t\t// away in the details below. Cells pad to a common width, four per row.\n\t\tconst plural = (n: number, s: string) => (n === 1 ? s : `${s}s`);\n\t\tconst cells: Array<{ key: CategoryKey; count: number; label: string }> = [];\n\t\tif (skills.length > 0) cells.push({ key: \"skills\", count: skills.length, label: plural(skills.length, \"skill\") });\n\t\tif (templates.length > 0) {\n\t\t\tcells.push({ key: \"commands\", count: templates.length, label: plural(templates.length, \"command\") });\n\t\t}\n\t\tif (agentCount > 0) {\n\t\t\tcells.push({ key: \"agents\", count: agentCount, label: plural(agentCount, \"agent\") });\n\t\t}\n\t\tif (mcpStatuses.length > 0) {\n\t\t\tcells.push({ key: \"mcp\", count: mcpStatuses.length, label: plural(mcpStatuses.length, \"mcp server\") });\n\t\t}\n\t\tif (pluginCount > 0) cells.push({ key: \"plugins\", count: pluginCount, label: plural(pluginCount, \"plugin\") });\n\t\tif (codeExtensionCount > 0) {\n\t\t\tcells.push({\n\t\t\t\tkey: \"extensions\",\n\t\t\t\tcount: codeExtensionCount,\n\t\t\t\tlabel: plural(codeExtensionCount, \"extension\"),\n\t\t\t});\n\t\t}\n\t\tif (customThemes.length > 0) {\n\t\t\tcells.push({ key: \"themes\", count: customThemes.length, label: plural(customThemes.length, \"theme\") });\n\t\t}\n\n\t\tif (cells.length > 0) {\n\t\t\tconst cellPlain = (c: (typeof cells)[number]) => `${CATEGORY_GLYPH[c.key]} ${c.count} ${c.label}`;\n\t\t\tconst cellWidth = Math.max(...cells.map((c) => visibleWidth(cellPlain(c)))) + 3;\n\t\t\tconst styledCell = (c: (typeof cells)[number]) => {\n\t\t\t\tconst pad = \" \".repeat(Math.max(0, cellWidth - visibleWidth(cellPlain(c))));\n\t\t\t\treturn (\n\t\t\t\t\t`${theme.fg(\"accent\", CATEGORY_GLYPH[c.key])} ` +\n\t\t\t\t\t`${theme.bold(String(c.count))} ${theme.fg(\"muted\", c.label)}${pad}`\n\t\t\t\t);\n\t\t\t};\n\t\t\tconst PER_ROW = 4;\n\t\t\tconst rows: string[] = [];\n\t\t\tfor (let i = 0; i < cells.length; i += PER_ROW) {\n\t\t\t\trows.push(\n\t\t\t\t\t`${RAIL}${cells\n\t\t\t\t\t\t.slice(i, i + PER_ROW)\n\t\t\t\t\t\t.map(styledCell)\n\t\t\t\t\t\t.join(\"\")}`.trimEnd(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tchatContainer.addChild(new Text(rows.join(\"\\n\"), 0, 0));\n\t\t} else {\n\t\t\tchatContainer.addChild(new Text(theme.fg(\"dim\", `${RAIL}no project resources loaded`), 0, 0));\n\t\t}\n\n\t\t// Context files stay visible in the summary — they shape every reply. Their\n\t\t// recurring cost is annotated in place rather than repeated as a warning\n\t\t// line below, so the file and its price stay in one spot.\n\t\tif (contextFiles.length > 0) {\n\t\t\tconst names = contextFiles\n\t\t\t\t.map((f) => formatContextPath(f.path, deps.getCwd()) + contextSizeNote(f))\n\t\t\t\t.join(\", \");\n\t\t\tchatContainer.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\t`${RAIL}${theme.fg(\"accent\", CATEGORY_GLYPH.context)} ${theme.fg(\"muted\", \"context\")} ${names}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// ── Details: names + scope-grouped paths, one keypress away ─────────────\n\t\t// A single expandable holds every per-category listing; collapsed it is\n\t\t// just the hint line, so a resource-heavy project no longer buries the\n\t\t// prompt under a wall of names at launch.\n\t\tconst detailSections: string[] = [];\n\t\t// Skills\n\t\tif (skills.length > 0) {\n\t\t\tconst groups = buildScopeGroups(\n\t\t\t\tskills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })),\n\t\t\t);\n\t\t\tconst skillList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => formatDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) => getShortPath(item.path, item.sourceInfo),\n\t\t\t});\n\t\t\tdetailSections.push(\n\t\t\t\t`${sectionHeader(\"Skills\")}\\n${formatCompactList(skills.map((skill) => skill.name))}\\n${skillList}`,\n\t\t\t);\n\t\t}\n\t\tif (templates.length > 0) {\n\t\t\tconst groups = buildScopeGroups(\n\t\t\t\ttemplates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })),\n\t\t\t);\n\t\t\tconst templateByPath = new Map(templates.map((t) => [t.filePath, t]));\n\t\t\tconst formatTemplate = (item: { path: string }) => {\n\t\t\t\tconst template = templateByPath.get(item.path);\n\t\t\t\treturn template ? `/${template.name}` : formatDisplayPath(item.path);\n\t\t\t};\n\t\t\tconst templateList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: formatTemplate,\n\t\t\t\tformatPackagePath: formatTemplate,\n\t\t\t});\n\t\t\tdetailSections.push(\n\t\t\t\t`${sectionHeader(\"Commands\")}\\n${formatCompactList(templates.map((t) => `/${t.name}`))}\\n${templateList}`,\n\t\t\t);\n\t\t}\n\t\t// Agents\n\t\tconst agents = deps.getAgents();\n\t\tif (agents.length > 0) {\n\t\t\tconst agentList = agents\n\t\t\t\t.map((agent) => {\n\t\t\t\t\tconst desc = summarizeAgentDescription(agent.description);\n\t\t\t\t\treturn theme.fg(\"dim\", ` ${agent.name} ${SEGMENT_SEP} ${desc}`);\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t\tdetailSections.push(`${sectionHeader(\"Agents\")}\\n${agentList}`);\n\t\t}\n\t\t// MCP\n\t\tif (mcpStatuses.length > 0) {\n\t\t\tconst mcpList = mcpStatuses\n\t\t\t\t.map((server) => {\n\t\t\t\t\tconst facts =\n\t\t\t\t\t\tserver.state === \"authorizing\"\n\t\t\t\t\t\t\t? [\"awaiting authorization\"]\n\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t`${server.toolCount} ${plural(server.toolCount, \"tool\")}`,\n\t\t\t\t\t\t\t\t\tserver.background ? \"background\" : \"foreground\",\n\t\t\t\t\t\t\t\t\t...(server.deferred ? [\"schemas deferred\"] : []),\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\treturn theme.fg(\"dim\", ` ${server.name} ${facts.join(` ${SEGMENT_SEP} `)}`);\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t\tdetailSections.push(`${sectionHeader(\"MCP\")}\\n${mcpList}`);\n\t\t}\n\t\t// Plugins\n\t\tconst plugins = extensions.filter((e) => displayNameOf(e)?.startsWith(\"plugin:\"));\n\t\tif (plugins.length > 0) {\n\t\t\tconst groups = buildScopeGroups(plugins);\n\t\t\tconst pluginList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => item.displayName ?? formatExtensionDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) =>\n\t\t\t\t\titem.displayName ?? formatExtensionDisplayPath(getShortPath(item.path, item.sourceInfo)),\n\t\t\t});\n\t\t\tdetailSections.push(`${sectionHeader(\"Plugins\")}\\n${pluginList}`);\n\t\t}\n\t\tif (extensions.length > 0) {\n\t\t\tconst groups = buildScopeGroups(extensions);\n\t\t\tconst extList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => item.displayName ?? formatExtensionDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) =>\n\t\t\t\t\titem.displayName ?? formatExtensionDisplayPath(getShortPath(item.path, item.sourceInfo)),\n\t\t\t});\n\t\t\tdetailSections.push(\n\t\t\t\t`${sectionHeader(\"Extensions\")}\\n${formatCompactList(getCompactExtensionLabels(extensions))}\\n${extList}`,\n\t\t\t);\n\t\t}\n\t\tif (customThemes.length > 0) {\n\t\t\tconst groups = buildScopeGroups(\n\t\t\t\tcustomThemes.map((loadedTheme) => ({\n\t\t\t\t\tpath: loadedTheme.sourcePath!,\n\t\t\t\t\tsourceInfo: loadedTheme.sourceInfo,\n\t\t\t\t})),\n\t\t\t);\n\t\t\tconst themeList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => formatDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) => getShortPath(item.path, item.sourceInfo),\n\t\t\t});\n\t\t\tdetailSections.push(`${sectionHeader(\"Themes\")}\\n${themeList}`);\n\t\t}\n\n\t\tif (detailSections.length > 0) {\n\t\t\tconst expandHint = theme.fg(\"dim\", `${RAIL}${appKeyLabel(\"app.tools.expand\")} details`);\n\t\t\tchatContainer.addChild(\n\t\t\t\tnew ExpandableText(\n\t\t\t\t\t() => expandHint,\n\t\t\t\t\t() => detailSections.join(\"\\n\\n\"),\n\t\t\t\t\tdeps.isExpanded(),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// Remaining context warnings are failures (unreadable files), not sizing\n\t\t// advice — size is reported inline on the context row above.\n\t\tif (contextWarnings.length > 0) {\n\t\t\tfor (const warning of contextWarnings) {\n\t\t\t\tchatContainer.addChild(new Text(theme.fg(\"warning\", `${RAIL}${warning}`), 0, 0));\n\t\t\t}\n\t\t}\n\n\t\t// Session state last: what the next turn will actually cost and run with.\n\t\tconst stateLine = deps.getStateLine?.();\n\t\tif (stateLine) {\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t\tchatContainer.addChild(new Text(`${RAIL}${stateLine}`, 0, 0));\n\t\t}\n\t}\n\n\tif (showDiagnostics) {\n\t\tconst skillDiagnostics = skillsResult.diagnostics;\n\t\tif (skillDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(skillDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Skill conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\tconst promptDiagnostics = promptsResult.diagnostics;\n\t\tif (promptDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(promptDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Prompt conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\tconst extensionDiagnostics: ResourceDiagnostic[] = [];\n\t\tconst extensionErrors = resourceLoader.getExtensions().errors;\n\t\tif (extensionErrors.length > 0) {\n\t\t\tfor (const error of extensionErrors) {\n\t\t\t\textensionDiagnostics.push({ type: \"error\", message: error.error, path: error.path });\n\t\t\t}\n\t\t}\n\n\t\tconst extensionRunner = deps.getExtensionRunner();\n\t\tconst commandDiagnostics = extensionRunner.getCommandDiagnostics();\n\t\textensionDiagnostics.push(...commandDiagnostics);\n\t\textensionDiagnostics.push(...deps.getBuiltInCommandConflictDiagnostics(extensionRunner));\n\n\t\tconst shortcutDiagnostics = extensionRunner.getShortcutDiagnostics();\n\t\textensionDiagnostics.push(...shortcutDiagnostics);\n\n\t\tif (extensionDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(extensionDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Extension issues]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\tconst themeDiagnostics = themesResult.diagnostics;\n\t\tif (themeDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(themeDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Theme conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"resource-display.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/resource-display.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,KAAK,SAAS,EAAU,IAAI,EAAgB,MAAM,0BAA0B,CAAC;AACtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAW5D,MAAM,WAAW,UAAU;IAC1B,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAQD,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU,CAE5D;AAED,qBAAa,cAAe,SAAQ,IAAK,YAAW,UAAU;IAE5D,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,QAAQ;IAFjB,YACS,SAAS,EAAE,MAAM,MAAM,EACvB,QAAQ,EAAE,MAAM,MAAM,EAC9B,iBAAiB,EAAE,OAAO,EAC1B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAGhB;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAEnC;CACD;AAMD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAUnD;AAsXD,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB;IACnC,aAAa,EAAE,SAAS,CAAC;IACzB,MAAM,IAAI,MAAM,CAAC;IACjB,iBAAiB,IAAI,cAAc,CAAC;IACpC,kBAAkB,IAAI,aAAa,CAAC,cAAc,CAAC,CAAC;IACpD,kBAAkB,IAAI,eAAe,CAAC;IACtC,aAAa,IAAI,MAAM,CAAC;IACxB,kBAAkB,IAAI,OAAO,CAAC;IAC9B,wEAAwE;IACxE,aAAa,IAAI,MAAM,CAAC;IACxB,sEAAsE;IACtE,SAAS,IAAI,eAAe,EAAE,CAAC;IAC/B;;;OAGG;IACH,YAAY,CAAC,IAAI,MAAM,GAAG,SAAS,CAAC;IACpC,YAAY,IAAI,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,UAAU,IAAI,OAAO,CAAC;IACtB,oCAAoC,CAAC,eAAe,EAAE,eAAe,GAAG,kBAAkB,EAAE,CAAC;CAC7F;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,cAAc,CAAC,EAC3D,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3B,OAAO,CAET;AAED,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,mBAAmB,EACzB,OAAO,CAAC,EAAE;IACT,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACnC,GACC,IAAI,CA+TN","sourcesContent":["/**\n * Startup/reload resource listing: path and source-label formatting, scope\n * grouping, diagnostics rendering, and the [Resources]/[Skills]/… sections\n * shown in the chat. Extracted from interactive-mode.ts; everything here is\n * either pure formatting or rendering into a container passed via deps.\n */\n\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { type Container, Spacer, Text, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentDefinition } from \"../../core/agent-frontmatter.js\";\nimport { summarizeAgentDescription } from \"../../core/agent-registry.js\";\nimport type { ContextFile } from \"../../core/context-files.js\";\nimport type { ExtensionRunner } from \"../../core/extensions/index.js\";\nimport { formatTokens } from \"../../core/format-tokens.js\";\nimport { getMcpServerStatuses } from \"../../core/mcp-status.js\";\nimport type { PromptTemplate } from \"../../core/prompt-templates.js\";\nimport type { ResourceDiagnostic, ResourceLoader } from \"../../core/resource-loader.js\";\nimport type { SourceInfo } from \"../../core/source-info.js\";\nimport { parseGitUrl } from \"../../utils/git.js\";\nimport { getCwdRelativePath } from \"../../utils/paths.js\";\nimport { CATEGORY_GLYPH, type CategoryKey, SEGMENT_SEP } from \"./brand.js\";\nimport { appKeyLabel } from \"./components/keybinding-hints.js\";\nimport { type ThemeColor, theme } from \"./theme/theme.js\";\n\n// ============================================================================\n// Expandable sections\n// ============================================================================\n\nexport interface Expandable {\n\tsetExpanded(expanded: boolean): void;\n}\n\n/**\n * Left rail for the startup summary. Lines up under the banner's owl glyph so\n * the whole first page reads as one column instead of three stray indents.\n */\nconst RAIL = \" \";\n\nexport function isExpandable(obj: unknown): obj is Expandable {\n\treturn typeof obj === \"object\" && obj !== null && \"setExpanded\" in obj;\n}\n\nexport class ExpandableText extends Text implements Expandable {\n\tconstructor(\n\t\tprivate collapsed: () => string,\n\t\tprivate expanded: () => string,\n\t\tinitiallyExpanded: boolean,\n\t\tpaddingX: number,\n\t\tpaddingY: number,\n\t) {\n\t\tsuper(initiallyExpanded ? expanded() : collapsed(), paddingX, paddingY);\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.setText(expanded ? this.expanded() : this.collapsed());\n\t}\n}\n\n// ============================================================================\n// Path and source-label formatting\n// ============================================================================\n\nexport function formatDisplayPath(p: string): string {\n\tconst home = os.homedir();\n\tlet result = p;\n\n\t// Replace home directory with ~\n\tif (result.startsWith(home)) {\n\t\tresult = `~${result.slice(home.length)}`;\n\t}\n\n\treturn result;\n}\n\nfunction formatExtensionDisplayPath(extensionPath: string): string {\n\tlet result = formatDisplayPath(extensionPath);\n\tresult = result.replace(/\\/index\\.ts$/, \"\").replace(/\\/index\\.js$/, \"\");\n\treturn result;\n}\n\nfunction formatContextPath(p: string, cwd: string): string {\n\tconst resolvedCwd = path.resolve(cwd);\n\tconst absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(resolvedCwd, p);\n\tconst relativePath = getCwdRelativePath(absolutePath, resolvedCwd);\n\tif (relativePath !== undefined) {\n\t\treturn relativePath;\n\t}\n\n\treturn formatDisplayPath(absolutePath);\n}\n\n/**\n * Inline cost note for a context file: these are injected into the system prompt\n * on every turn, so an oversized one is priced where it is listed instead of in\n * a separate warning line.\n */\nfunction contextSizeNote(file: ContextFile): string {\n\tif (!file.size) return \"\";\n\tconst advice = file.size === \"truncated\" ? \"truncated\" : \"consider trimming\";\n\tconst tokens = file.tokens === undefined ? undefined : `~${formatTokens(file.tokens)} tokens`;\n\treturn theme.fg(\"warning\", ` ${tokens ? `${tokens} ${SEGMENT_SEP} ` : \"\"}${advice}`);\n}\n\n/**\n * Get a short path relative to the package root for display.\n */\nfunction getShortPath(fullPath: string, sourceInfo?: SourceInfo): string {\n\tconst baseDir = sourceInfo?.baseDir;\n\tif (baseDir && isPackageSource(sourceInfo)) {\n\t\tconst relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));\n\t\tif (\n\t\t\trelativePath &&\n\t\t\trelativePath !== \".\" &&\n\t\t\t!relativePath.startsWith(\"..\") &&\n\t\t\t!relativePath.startsWith(`..${path.sep}`) &&\n\t\t\t!path.isAbsolute(relativePath)\n\t\t) {\n\t\t\treturn relativePath.replace(/\\\\/g, \"/\");\n\t\t}\n\t}\n\n\tconst source = sourceInfo?.source ?? \"\";\n\tconst npmMatch = fullPath.match(/node_modules\\/(@?[^/]+(?:\\/[^/]+)?)\\/(.*)/);\n\tif (npmMatch && source.startsWith(\"npm:\")) {\n\t\treturn npmMatch[2];\n\t}\n\n\tconst gitMatch = fullPath.match(/git\\/[^/]+\\/[^/]+\\/(.*)/);\n\tif (gitMatch && source.startsWith(\"git:\")) {\n\t\treturn gitMatch[1];\n\t}\n\n\treturn formatDisplayPath(fullPath);\n}\n\nfunction getCompactPathLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\tconst shortPath = getShortPath(resourcePath, sourceInfo);\n\tconst normalizedPath = shortPath.replace(/\\\\/g, \"/\");\n\tconst segments = normalizedPath.split(\"/\").filter((segment) => segment.length > 0 && segment !== \"~\");\n\tif (segments.length > 0) {\n\t\treturn segments[segments.length - 1]!;\n\t}\n\treturn shortPath;\n}\n\nfunction getCompactPackageSourceLabel(sourceInfo?: SourceInfo): string {\n\tconst source = sourceInfo?.source ?? \"\";\n\tif (source.startsWith(\"npm:\")) {\n\t\treturn source.slice(\"npm:\".length) || source;\n\t}\n\n\tconst gitSource = parseGitUrl(source);\n\tif (gitSource) {\n\t\treturn gitSource.path || source;\n\t}\n\n\treturn source;\n}\n\nfunction getCompactExtensionLabel(resourcePath: string, sourceInfo?: SourceInfo): string {\n\tif (!isPackageSource(sourceInfo)) {\n\t\treturn getCompactPathLabel(resourcePath, sourceInfo);\n\t}\n\n\tconst sourceLabel = getCompactPackageSourceLabel(sourceInfo);\n\tif (!sourceLabel) {\n\t\treturn getCompactPathLabel(resourcePath, sourceInfo);\n\t}\n\n\tconst shortPath = getShortPath(resourcePath, sourceInfo).replace(/\\\\/g, \"/\");\n\tconst packagePath = shortPath.startsWith(\"extensions/\") ? shortPath.slice(\"extensions/\".length) : shortPath;\n\tconst parsedPath = path.posix.parse(packagePath);\n\n\tif (parsedPath.name === \"index\") {\n\t\treturn !parsedPath.dir || parsedPath.dir === \".\" ? sourceLabel : `${sourceLabel}:${parsedPath.dir}`;\n\t}\n\n\treturn `${sourceLabel}:${packagePath}`;\n}\n\nfunction getCompactDisplayPathSegments(resourcePath: string): string[] {\n\treturn formatDisplayPath(resourcePath)\n\t\t.replace(/\\\\/g, \"/\")\n\t\t.split(\"/\")\n\t\t.filter((segment) => segment.length > 0 && segment !== \"~\");\n}\n\nfunction getCompactNonPackageExtensionLabel(\n\tresourcePath: string,\n\tindex: number,\n\tallPaths: Array<{ path: string; segments: string[] }>,\n): string {\n\tconst segments = allPaths[index]?.segments;\n\tif (!segments || segments.length === 0) {\n\t\treturn getCompactPathLabel(resourcePath);\n\t}\n\n\tfor (let segmentCount = 1; segmentCount <= segments.length; segmentCount += 1) {\n\t\tconst candidate = segments.slice(-segmentCount).join(\"/\");\n\t\tconst isUnique = allPaths.every((item, itemIndex) => {\n\t\t\tif (itemIndex === index) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn item.segments.slice(-segmentCount).join(\"/\") !== candidate;\n\t\t});\n\n\t\tif (isUnique) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\treturn segments.join(\"/\");\n}\n\nfunction getCompactExtensionLabels(\n\textensions: Array<{ path: string; sourceInfo?: SourceInfo; displayName?: string }>,\n): string[] {\n\tconst nonPackageExtensions = extensions\n\t\t.map((extension) => {\n\t\t\tconst segments = getCompactDisplayPathSegments(extension.path);\n\t\t\tconst lastSegment = segments[segments.length - 1];\n\t\t\tif (segments.length > 1 && (lastSegment === \"index.ts\" || lastSegment === \"index.js\")) {\n\t\t\t\tsegments.pop();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tpath: extension.path,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t\tsegments,\n\t\t\t};\n\t\t})\n\t\t.filter((extension) => !isPackageSource(extension.sourceInfo));\n\n\treturn extensions.map((extension) => {\n\t\tif (extension.displayName) {\n\t\t\treturn extension.displayName;\n\t\t}\n\n\t\tif (isPackageSource(extension.sourceInfo)) {\n\t\t\treturn getCompactExtensionLabel(extension.path, extension.sourceInfo);\n\t\t}\n\n\t\tconst nonPackageIndex = nonPackageExtensions.findIndex((item) => item.path === extension.path);\n\t\tif (nonPackageIndex === -1) {\n\t\t\treturn getCompactPathLabel(extension.path, extension.sourceInfo);\n\t\t}\n\n\t\treturn getCompactNonPackageExtensionLabel(extension.path, nonPackageIndex, nonPackageExtensions);\n\t});\n}\n\nfunction getDisplaySourceInfo(sourceInfo?: SourceInfo): {\n\tlabel: string;\n\tscopeLabel?: string;\n\tcolor: \"accent\" | \"muted\";\n} {\n\tconst source = sourceInfo?.source ?? \"local\";\n\tconst scope = sourceInfo?.scope ?? \"project\";\n\tif (source === \"local\") {\n\t\tif (scope === \"user\") {\n\t\t\treturn { label: \"user\", color: \"muted\" };\n\t\t}\n\t\tif (scope === \"project\") {\n\t\t\treturn { label: \"project\", color: \"muted\" };\n\t\t}\n\t\tif (scope === \"temporary\") {\n\t\t\treturn { label: \"path\", scopeLabel: \"temp\", color: \"muted\" };\n\t\t}\n\t\treturn { label: \"path\", color: \"muted\" };\n\t}\n\n\tif (source === \"cli\") {\n\t\treturn { label: \"path\", scopeLabel: scope === \"temporary\" ? \"temp\" : undefined, color: \"muted\" };\n\t}\n\n\tconst scopeLabel =\n\t\tscope === \"user\" ? \"user\" : scope === \"project\" ? \"project\" : scope === \"temporary\" ? \"temp\" : undefined;\n\treturn { label: source, scopeLabel, color: \"accent\" };\n}\n\nfunction getScopeGroup(sourceInfo?: SourceInfo): \"user\" | \"project\" | \"path\" {\n\tconst source = sourceInfo?.source ?? \"local\";\n\tconst scope = sourceInfo?.scope ?? \"project\";\n\tif (source === \"cli\" || scope === \"temporary\") return \"path\";\n\tif (scope === \"user\") return \"user\";\n\tif (scope === \"project\") return \"project\";\n\treturn \"path\";\n}\n\nfunction isPackageSource(sourceInfo?: SourceInfo): boolean {\n\tconst source = sourceInfo?.source ?? \"\";\n\treturn source.startsWith(\"npm:\") || source.startsWith(\"git:\");\n}\n\n// ============================================================================\n// Scope grouping and diagnostics\n// ============================================================================\n\ninterface ScopedItem {\n\tpath: string;\n\tsourceInfo?: SourceInfo;\n\tdisplayName?: string;\n}\n\ninterface ScopeGroup {\n\tscope: \"user\" | \"project\" | \"path\";\n\tpaths: ScopedItem[];\n\tpackages: Map<string, ScopedItem[]>;\n}\n\nfunction buildScopeGroups(items: ScopedItem[]): ScopeGroup[] {\n\tconst groups: Record<\"user\" | \"project\" | \"path\", ScopeGroup> = {\n\t\tuser: { scope: \"user\", paths: [], packages: new Map() },\n\t\tproject: { scope: \"project\", paths: [], packages: new Map() },\n\t\tpath: { scope: \"path\", paths: [], packages: new Map() },\n\t};\n\n\tfor (const item of items) {\n\t\tconst groupKey = getScopeGroup(item.sourceInfo);\n\t\tconst group = groups[groupKey];\n\t\tconst source = item.sourceInfo?.source ?? \"local\";\n\n\t\tif (isPackageSource(item.sourceInfo)) {\n\t\t\tconst list = group.packages.get(source) ?? [];\n\t\t\tlist.push(item);\n\t\t\tgroup.packages.set(source, list);\n\t\t} else {\n\t\t\tgroup.paths.push(item);\n\t\t}\n\t}\n\n\treturn [groups.project, groups.user, groups.path].filter(\n\t\t(group) => group.paths.length > 0 || group.packages.size > 0,\n\t);\n}\n\nfunction formatScopeGroups(\n\tgroups: ScopeGroup[],\n\toptions: {\n\t\tformatPath: (item: ScopedItem) => string;\n\t\tformatPackagePath: (item: ScopedItem, source: string) => string;\n\t},\n): string {\n\tconst lines: string[] = [];\n\n\tfor (const group of groups) {\n\t\tlines.push(` ${theme.fg(\"accent\", group.scope)}`);\n\n\t\tconst sortedPaths = [...group.paths].sort((a, b) => a.path.localeCompare(b.path));\n\t\tfor (const item of sortedPaths) {\n\t\t\tlines.push(theme.fg(\"dim\", ` ${options.formatPath(item)}`));\n\t\t}\n\n\t\tconst sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));\n\t\tfor (const [source, items] of sortedPackages) {\n\t\t\tlines.push(` ${theme.fg(\"mdLink\", source)}`);\n\t\t\tconst sortedPackagePaths = [...items].sort((a, b) => a.path.localeCompare(b.path));\n\t\t\tfor (const item of sortedPackagePaths) {\n\t\t\t\tlines.push(theme.fg(\"dim\", ` ${options.formatPackagePath(item, source)}`));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction findSourceInfoForPath(p: string, sourceInfos: Map<string, SourceInfo>): SourceInfo | undefined {\n\tconst exact = sourceInfos.get(p);\n\tif (exact) return exact;\n\n\tlet current = p;\n\twhile (current.includes(\"/\")) {\n\t\tcurrent = current.substring(0, current.lastIndexOf(\"/\"));\n\t\tconst parent = sourceInfos.get(current);\n\t\tif (parent) return parent;\n\t}\n\n\treturn undefined;\n}\n\nfunction formatPathWithSource(p: string, sourceInfo?: SourceInfo): string {\n\tif (sourceInfo) {\n\t\tconst shortPath = getShortPath(p, sourceInfo);\n\t\tconst { label, scopeLabel } = getDisplaySourceInfo(sourceInfo);\n\t\tconst labelText = scopeLabel ? `${label} (${scopeLabel})` : label;\n\t\treturn `${labelText} ${shortPath}`;\n\t}\n\treturn formatDisplayPath(p);\n}\n\nfunction formatDiagnostics(diagnostics: readonly ResourceDiagnostic[], sourceInfos: Map<string, SourceInfo>): string {\n\tconst lines: string[] = [];\n\n\t// Group collision diagnostics by name\n\tconst collisions = new Map<string, ResourceDiagnostic[]>();\n\tconst otherDiagnostics: ResourceDiagnostic[] = [];\n\n\tfor (const d of diagnostics) {\n\t\tif (d.type === \"collision\" && d.collision) {\n\t\t\tconst list = collisions.get(d.collision.name) ?? [];\n\t\t\tlist.push(d);\n\t\t\tcollisions.set(d.collision.name, list);\n\t\t} else {\n\t\t\totherDiagnostics.push(d);\n\t\t}\n\t}\n\n\t// Format collision diagnostics grouped by name\n\tfor (const [name, collisionList] of collisions) {\n\t\tconst first = collisionList[0]?.collision;\n\t\tif (!first) continue;\n\t\tlines.push(theme.fg(\"warning\", ` \"${name}\" collision:`));\n\t\tlines.push(\n\t\t\ttheme.fg(\n\t\t\t\t\"dim\",\n\t\t\t\t` ${theme.fg(\"success\", \"✓\")} ${formatPathWithSource(first.winnerPath, findSourceInfoForPath(first.winnerPath, sourceInfos))}`,\n\t\t\t),\n\t\t);\n\t\tfor (const d of collisionList) {\n\t\t\tif (d.collision) {\n\t\t\t\tlines.push(\n\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\"dim\",\n\t\t\t\t\t\t` ${theme.fg(\"warning\", \"✗\")} ${formatPathWithSource(d.collision.loserPath, findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const d of otherDiagnostics) {\n\t\tif (d.path) {\n\t\t\tconst formattedPath = formatPathWithSource(d.path, findSourceInfoForPath(d.path, sourceInfos));\n\t\t\tlines.push(theme.fg(d.type === \"error\" ? \"error\" : \"warning\", ` ${formattedPath}`));\n\t\t\tlines.push(theme.fg(d.type === \"error\" ? \"error\" : \"warning\", ` ${d.message}`));\n\t\t} else {\n\t\t\tlines.push(theme.fg(d.type === \"error\" ? \"error\" : \"warning\", ` ${d.message}`));\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// ============================================================================\n// Loaded-resources listing\n// ============================================================================\n\n/** The slice of the interactive mode the resource listing renders into. */\nexport interface ResourceDisplayDeps {\n\tchatContainer: Container;\n\tgetCwd(): string;\n\tgetResourceLoader(): ResourceLoader;\n\tgetPromptTemplates(): ReadonlyArray<PromptTemplate>;\n\tgetExtensionRunner(): ExtensionRunner;\n\tgetActiveMode(): string;\n\tgetSubagentEnabled(): boolean;\n\t/** Dispatchable subagents in this cwd (0 when the Task tool is off). */\n\tgetAgentCount(): number;\n\t/** Dispatchable agents for this cwd (empty when Task tool is off). */\n\tgetAgents(): AgentDefinition[];\n\t/**\n\t * One-line session state (model, thinking level, auth caveats) shown under the\n\t * summary. Undefined when there is nothing worth stating.\n\t */\n\tgetStateLine?(): string | undefined;\n\tquietStartup(): boolean;\n\tverbose: boolean;\n\t/** Startup expansion state for the collapsible sections. */\n\tisExpanded(): boolean;\n\tgetBuiltInCommandConflictDiagnostics(extensionRunner: ExtensionRunner): ResourceDiagnostic[];\n}\n\n/**\n * Whether a `showLoadedResources` call renders the full summary rather than\n * diagnostics only. Exported so callers can skip preparing summary-only data\n * (and claiming once-per-session notices) on a quiet startup.\n */\nexport function willShowResourceListing(\n\tdeps: Pick<ResourceDisplayDeps, \"verbose\" | \"quietStartup\">,\n\toptions?: { force?: boolean },\n): boolean {\n\treturn options?.force === true || deps.verbose || !deps.quietStartup();\n}\n\nexport function showLoadedResources(\n\tdeps: ResourceDisplayDeps,\n\toptions?: {\n\t\textensions?: Array<{ path: string; sourceInfo?: SourceInfo }>;\n\t\tforce?: boolean;\n\t\tshowDiagnosticsWhenQuiet?: boolean;\n\t},\n): void {\n\tconst showListing = willShowResourceListing(deps, options);\n\tconst showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;\n\tif (!showListing && !showDiagnostics) {\n\t\treturn;\n\t}\n\n\tconst resourceLoader = deps.getResourceLoader();\n\tconst chatContainer = deps.chatContainer;\n\n\tconst sectionHeader = (name: string, color: ThemeColor = \"mdHeading\") => theme.fg(color, `[${name}]`);\n\tconst formatCompactList = (items: string[], listOptions?: { sort?: boolean }): string => {\n\t\tconst labels = items.map((item) => item.trim()).filter((item) => item.length > 0);\n\t\tif (listOptions?.sort !== false) {\n\t\t\tlabels.sort((a, b) => a.localeCompare(b));\n\t\t}\n\t\treturn theme.fg(\"dim\", ` ${labels.join(\", \")}`);\n\t};\n\n\tconst skillsResult = resourceLoader.getSkills();\n\tconst promptsResult = resourceLoader.getPrompts();\n\tconst themesResult = resourceLoader.getThemes();\n\tconst extensions =\n\t\toptions?.extensions ??\n\t\tresourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.filter((extension) => !extension.internal)\n\t\t\t.map((extension) => ({\n\t\t\t\tpath: extension.path,\n\t\t\t\tsourceInfo: extension.sourceInfo,\n\t\t\t\tdisplayName: extension.displayName,\n\t\t\t}));\n\tconst sourceInfos = new Map<string, SourceInfo>();\n\tfor (const extension of extensions) {\n\t\tif (extension.sourceInfo) {\n\t\t\tsourceInfos.set(extension.path, extension.sourceInfo);\n\t\t}\n\t}\n\tfor (const skill of skillsResult.skills) {\n\t\tif (skill.sourceInfo) {\n\t\t\tsourceInfos.set(skill.filePath, skill.sourceInfo);\n\t\t}\n\t}\n\tfor (const prompt of promptsResult.prompts) {\n\t\tif (prompt.sourceInfo) {\n\t\t\tsourceInfos.set(prompt.filePath, prompt.sourceInfo);\n\t\t}\n\t}\n\tfor (const loadedTheme of themesResult.themes) {\n\t\tif (loadedTheme.sourcePath && loadedTheme.sourceInfo) {\n\t\t\tsourceInfos.set(loadedTheme.sourcePath, loadedTheme.sourceInfo);\n\t\t}\n\t}\n\n\tif (showListing) {\n\t\tchatContainer.addChild(new Spacer(1));\n\n\t\tconst { agentsFiles: contextFiles, warnings: contextWarnings } = resourceLoader.getAgentsFiles();\n\t\tconst skills = skillsResult.skills;\n\t\tconst templates = deps.getPromptTemplates();\n\t\tconst loadedThemes = themesResult.themes;\n\t\tconst customThemes = loadedThemes.filter((t) => t.sourcePath);\n\t\tconst agentCount = deps.getAgentCount();\n\t\t// Live servers, whatever their source (mcp.json, per-server files, plugins) —\n\t\t// the loader records each connect outcome, so this is what the session can\n\t\t// actually call, not what was merely configured.\n\t\tconst mcpStatuses = getMcpServerStatuses();\n\t\t// Plugins register their synthetic extension factory as `plugin:<id>`;\n\t\t// split them out so plugins and code extensions each get their own count.\n\t\t// (displayName is absent from the options-provided extension shape, so read\n\t\t// it structurally.)\n\t\tconst displayNameOf = (e: object): string | undefined => {\n\t\t\tconst value = (e as { displayName?: unknown }).displayName;\n\t\t\treturn typeof value === \"string\" ? value : undefined;\n\t\t};\n\t\tconst pluginCount = extensions.filter((e) => displayNameOf(e)?.startsWith(\"plugin:\")).length;\n\t\tconst codeExtensionCount = extensions.length - pluginCount;\n\n\t\t// ── Counted capability grid ──────────────────────────────────────────────\n\t\t// One cell per loaded capability class (glyph + count + label), so \"what\n\t\t// can this session do\" reads at a glance; names/paths sit one keypress\n\t\t// away in the details below. Cells pad to a common width, four per row.\n\t\tconst plural = (n: number, s: string) => (n === 1 ? s : `${s}s`);\n\t\tconst cells: Array<{ key: CategoryKey; count: number; label: string }> = [];\n\t\tif (skills.length > 0) cells.push({ key: \"skills\", count: skills.length, label: plural(skills.length, \"skill\") });\n\t\tif (templates.length > 0) {\n\t\t\tcells.push({ key: \"commands\", count: templates.length, label: plural(templates.length, \"command\") });\n\t\t}\n\t\tif (agentCount > 0) {\n\t\t\tcells.push({ key: \"agents\", count: agentCount, label: plural(agentCount, \"agent\") });\n\t\t}\n\t\tif (mcpStatuses.length > 0) {\n\t\t\tcells.push({ key: \"mcp\", count: mcpStatuses.length, label: plural(mcpStatuses.length, \"mcp server\") });\n\t\t}\n\t\tif (pluginCount > 0) cells.push({ key: \"plugins\", count: pluginCount, label: plural(pluginCount, \"plugin\") });\n\t\tif (codeExtensionCount > 0) {\n\t\t\tcells.push({\n\t\t\t\tkey: \"extensions\",\n\t\t\t\tcount: codeExtensionCount,\n\t\t\t\tlabel: plural(codeExtensionCount, \"extension\"),\n\t\t\t});\n\t\t}\n\t\tif (customThemes.length > 0) {\n\t\t\tcells.push({ key: \"themes\", count: customThemes.length, label: plural(customThemes.length, \"theme\") });\n\t\t}\n\n\t\tif (cells.length > 0) {\n\t\t\tconst cellPlain = (c: (typeof cells)[number]) => `${CATEGORY_GLYPH[c.key]} ${c.count} ${c.label}`;\n\t\t\tconst cellWidth = Math.max(...cells.map((c) => visibleWidth(cellPlain(c)))) + 3;\n\t\t\tconst styledCell = (c: (typeof cells)[number]) => {\n\t\t\t\tconst pad = \" \".repeat(Math.max(0, cellWidth - visibleWidth(cellPlain(c))));\n\t\t\t\treturn (\n\t\t\t\t\t`${theme.fg(\"accent\", CATEGORY_GLYPH[c.key])} ` +\n\t\t\t\t\t`${theme.bold(String(c.count))} ${theme.fg(\"muted\", c.label)}${pad}`\n\t\t\t\t);\n\t\t\t};\n\t\t\tconst PER_ROW = 4;\n\t\t\tconst rows: string[] = [];\n\t\t\tfor (let i = 0; i < cells.length; i += PER_ROW) {\n\t\t\t\trows.push(\n\t\t\t\t\t`${RAIL}${cells\n\t\t\t\t\t\t.slice(i, i + PER_ROW)\n\t\t\t\t\t\t.map(styledCell)\n\t\t\t\t\t\t.join(\"\")}`.trimEnd(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tchatContainer.addChild(new Text(rows.join(\"\\n\"), 0, 0));\n\t\t} else {\n\t\t\tchatContainer.addChild(new Text(theme.fg(\"dim\", `${RAIL}no project resources loaded`), 0, 0));\n\t\t}\n\n\t\t// Context files stay visible in the summary — they shape every reply. Their\n\t\t// recurring cost is annotated in place rather than repeated as a warning\n\t\t// line below, so the file and its price stay in one spot.\n\t\tif (contextFiles.length > 0) {\n\t\t\tconst names = contextFiles\n\t\t\t\t.map((f) => formatContextPath(f.path, deps.getCwd()) + contextSizeNote(f))\n\t\t\t\t.join(\", \");\n\t\t\tchatContainer.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\t`${RAIL}${theme.fg(\"accent\", CATEGORY_GLYPH.context)} ${theme.fg(\"muted\", \"context\")} ${names}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// ── Details: names + scope-grouped paths, one keypress away ─────────────\n\t\t// A single expandable holds every per-category listing; collapsed it is\n\t\t// just the hint line, so a resource-heavy project no longer buries the\n\t\t// prompt under a wall of names at launch.\n\t\tconst detailSections: string[] = [];\n\t\t// Skills\n\t\tif (skills.length > 0) {\n\t\t\tconst groups = buildScopeGroups(\n\t\t\t\tskills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })),\n\t\t\t);\n\t\t\tconst skillList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => formatDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) => getShortPath(item.path, item.sourceInfo),\n\t\t\t});\n\t\t\tdetailSections.push(\n\t\t\t\t`${sectionHeader(\"Skills\")}\\n${formatCompactList(skills.map((skill) => skill.name))}\\n${skillList}`,\n\t\t\t);\n\t\t}\n\t\tif (templates.length > 0) {\n\t\t\tconst groups = buildScopeGroups(\n\t\t\t\ttemplates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })),\n\t\t\t);\n\t\t\tconst templateByPath = new Map(templates.map((t) => [t.filePath, t]));\n\t\t\tconst formatTemplate = (item: { path: string }) => {\n\t\t\t\tconst template = templateByPath.get(item.path);\n\t\t\t\treturn template ? `/${template.name}` : formatDisplayPath(item.path);\n\t\t\t};\n\t\t\tconst templateList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: formatTemplate,\n\t\t\t\tformatPackagePath: formatTemplate,\n\t\t\t});\n\t\t\tdetailSections.push(\n\t\t\t\t`${sectionHeader(\"Commands\")}\\n${formatCompactList(templates.map((t) => `/${t.name}`))}\\n${templateList}`,\n\t\t\t);\n\t\t}\n\t\t// Agents\n\t\tconst agents = deps.getAgents();\n\t\tif (agents.length > 0) {\n\t\t\tconst agentList = agents\n\t\t\t\t.map((agent) => {\n\t\t\t\t\tconst desc = summarizeAgentDescription(agent.description);\n\t\t\t\t\treturn theme.fg(\"dim\", ` ${agent.name} ${SEGMENT_SEP} ${desc}`);\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t\tdetailSections.push(`${sectionHeader(\"Agents\")}\\n${agentList}`);\n\t\t}\n\t\t// MCP\n\t\tif (mcpStatuses.length > 0) {\n\t\t\tconst mcpList = mcpStatuses\n\t\t\t\t.map((server) => {\n\t\t\t\t\tconst facts =\n\t\t\t\t\t\tserver.state === \"authorizing\"\n\t\t\t\t\t\t\t? [\"awaiting authorization\"]\n\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t`${server.toolCount} ${plural(server.toolCount, \"tool\")}`,\n\t\t\t\t\t\t\t\t\tserver.background ? \"background\" : \"foreground\",\n\t\t\t\t\t\t\t\t\t...(server.deferred ? [\"schemas deferred\"] : []),\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\treturn theme.fg(\"dim\", ` ${server.name} ${facts.join(` ${SEGMENT_SEP} `)}`);\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t\tdetailSections.push(`${sectionHeader(\"MCP\")}\\n${mcpList}`);\n\t\t}\n\t\t// Plugins\n\t\tconst plugins = extensions.filter((e) => displayNameOf(e)?.startsWith(\"plugin:\"));\n\t\tif (plugins.length > 0) {\n\t\t\tconst groups = buildScopeGroups(plugins);\n\t\t\tconst pluginList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => item.displayName ?? formatExtensionDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) =>\n\t\t\t\t\titem.displayName ?? formatExtensionDisplayPath(getShortPath(item.path, item.sourceInfo)),\n\t\t\t});\n\t\t\tdetailSections.push(`${sectionHeader(\"Plugins\")}\\n${pluginList}`);\n\t\t}\n\t\tif (extensions.length > 0) {\n\t\t\tconst groups = buildScopeGroups(extensions);\n\t\t\tconst extList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => item.displayName ?? formatExtensionDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) =>\n\t\t\t\t\titem.displayName ?? formatExtensionDisplayPath(getShortPath(item.path, item.sourceInfo)),\n\t\t\t});\n\t\t\tdetailSections.push(\n\t\t\t\t`${sectionHeader(\"Extensions\")}\\n${formatCompactList(getCompactExtensionLabels(extensions))}\\n${extList}`,\n\t\t\t);\n\t\t}\n\t\tif (customThemes.length > 0) {\n\t\t\tconst groups = buildScopeGroups(\n\t\t\t\tcustomThemes.map((loadedTheme) => ({\n\t\t\t\t\tpath: loadedTheme.sourcePath!,\n\t\t\t\t\tsourceInfo: loadedTheme.sourceInfo,\n\t\t\t\t})),\n\t\t\t);\n\t\t\tconst themeList = formatScopeGroups(groups, {\n\t\t\t\tformatPath: (item) => formatDisplayPath(item.path),\n\t\t\t\tformatPackagePath: (item) => getShortPath(item.path, item.sourceInfo),\n\t\t\t});\n\t\t\tdetailSections.push(`${sectionHeader(\"Themes\")}\\n${themeList}`);\n\t\t}\n\n\t\tif (detailSections.length > 0) {\n\t\t\tconst expandHint = theme.fg(\"dim\", `${RAIL}${appKeyLabel(\"app.tools.expand\")} details`);\n\t\t\tchatContainer.addChild(\n\t\t\t\tnew ExpandableText(\n\t\t\t\t\t() => expandHint,\n\t\t\t\t\t() => detailSections.join(\"\\n\\n\"),\n\t\t\t\t\tdeps.isExpanded(),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// Remaining context warnings are failures (unreadable files), not sizing\n\t\t// advice — size is reported inline on the context row above.\n\t\tif (contextWarnings.length > 0) {\n\t\t\tfor (const warning of contextWarnings) {\n\t\t\t\tchatContainer.addChild(new Text(theme.fg(\"warning\", `${RAIL}${warning}`), 0, 0));\n\t\t\t}\n\t\t}\n\n\t\t// Session state last: what the next turn will actually cost and run with.\n\t\tconst stateLine = deps.getStateLine?.();\n\t\tif (stateLine) {\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t\tchatContainer.addChild(new Text(`${RAIL}${stateLine}`, 0, 0));\n\t\t}\n\t}\n\n\tif (showDiagnostics) {\n\t\tconst skillDiagnostics = skillsResult.diagnostics;\n\t\tif (skillDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(skillDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Skill conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\tconst promptDiagnostics = promptsResult.diagnostics;\n\t\tif (promptDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(promptDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Prompt conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\tconst extensionDiagnostics: ResourceDiagnostic[] = [];\n\t\tconst extensionErrors = resourceLoader.getExtensions().errors;\n\t\tif (extensionErrors.length > 0) {\n\t\t\tfor (const error of extensionErrors) {\n\t\t\t\textensionDiagnostics.push({ type: \"error\", message: error.error, path: error.path });\n\t\t\t}\n\t\t}\n\n\t\tconst extensionRunner = deps.getExtensionRunner();\n\t\tconst commandDiagnostics = extensionRunner.getCommandDiagnostics();\n\t\textensionDiagnostics.push(...commandDiagnostics);\n\t\textensionDiagnostics.push(...deps.getBuiltInCommandConflictDiagnostics(extensionRunner));\n\n\t\tconst shortcutDiagnostics = extensionRunner.getShortcutDiagnostics();\n\t\textensionDiagnostics.push(...shortcutDiagnostics);\n\n\t\tif (extensionDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(extensionDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Extension issues]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\tconst themeDiagnostics = themesResult.diagnostics;\n\t\tif (themeDiagnostics.length > 0) {\n\t\t\tconst warningLines = formatDiagnostics(themeDiagnostics, sourceInfos);\n\t\t\tchatContainer.addChild(new Text(`${theme.fg(\"warning\", \"[Theme conflicts]\")}\\n${warningLines}`, 0, 0));\n\t\t\tchatContainer.addChild(new Spacer(1));\n\t\t}\n\t}\n}\n"]}
@@ -47,12 +47,12 @@ export function formatDisplayPath(p) {
47
47
  }
48
48
  return result;
49
49
  }
50
- export function formatExtensionDisplayPath(extensionPath) {
50
+ function formatExtensionDisplayPath(extensionPath) {
51
51
  let result = formatDisplayPath(extensionPath);
52
52
  result = result.replace(/\/index\.ts$/, "").replace(/\/index\.js$/, "");
53
53
  return result;
54
54
  }
55
- export function formatContextPath(p, cwd) {
55
+ function formatContextPath(p, cwd) {
56
56
  const resolvedCwd = path.resolve(cwd);
57
57
  const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(resolvedCwd, p);
58
58
  const relativePath = getCwdRelativePath(absolutePath, resolvedCwd);
@@ -66,7 +66,7 @@ export function formatContextPath(p, cwd) {
66
66
  * on every turn, so an oversized one is priced where it is listed instead of in
67
67
  * a separate warning line.
68
68
  */
69
- export function contextSizeNote(file) {
69
+ function contextSizeNote(file) {
70
70
  if (!file.size)
71
71
  return "";
72
72
  const advice = file.size === "truncated" ? "truncated" : "consider trimming";
@@ -76,7 +76,7 @@ export function contextSizeNote(file) {
76
76
  /**
77
77
  * Get a short path relative to the package root for display.
78
78
  */
79
- export function getShortPath(fullPath, sourceInfo) {
79
+ function getShortPath(fullPath, sourceInfo) {
80
80
  const baseDir = sourceInfo?.baseDir;
81
81
  if (baseDir && isPackageSource(sourceInfo)) {
82
82
  const relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));
@@ -99,7 +99,7 @@ export function getShortPath(fullPath, sourceInfo) {
99
99
  }
100
100
  return formatDisplayPath(fullPath);
101
101
  }
102
- export function getCompactPathLabel(resourcePath, sourceInfo) {
102
+ function getCompactPathLabel(resourcePath, sourceInfo) {
103
103
  const shortPath = getShortPath(resourcePath, sourceInfo);
104
104
  const normalizedPath = shortPath.replace(/\\/g, "/");
105
105
  const segments = normalizedPath.split("/").filter((segment) => segment.length > 0 && segment !== "~");
@@ -160,7 +160,7 @@ function getCompactNonPackageExtensionLabel(resourcePath, index, allPaths) {
160
160
  }
161
161
  return segments.join("/");
162
162
  }
163
- export function getCompactExtensionLabels(extensions) {
163
+ function getCompactExtensionLabels(extensions) {
164
164
  const nonPackageExtensions = extensions
165
165
  .map((extension) => {
166
166
  const segments = getCompactDisplayPathSegments(extension.path);
@@ -287,7 +287,7 @@ function formatPathWithSource(p, sourceInfo) {
287
287
  }
288
288
  return formatDisplayPath(p);
289
289
  }
290
- export function formatDiagnostics(diagnostics, sourceInfos) {
290
+ function formatDiagnostics(diagnostics, sourceInfos) {
291
291
  const lines = [];
292
292
  // Group collision diagnostics by name
293
293
  const collisions = new Map();