@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
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/extensions/core/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hoo-config.json — types, I/O, and merge rules shared by the hoo-core extensions.\n *\n * Config merge order (lowest → highest priority):\n * 1. ~/.hoocode/hoo-config.json (global defaults)\n * 2. ./.hoocode/hoo-config.json (project overrides — scalars win; arrays union)\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport { getHooCodeDir } from \"../../config.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\nconst GLOBAL_CONFIG_PATH = join(HOOCODE_DIR, \"hoo-config.json\");\n\nexport interface ModeConfig {\n\t/** Tool names that bypass the permission gate in this mode */\n\tauto_allow?: string[];\n\t/** Tool names available in this mode (if set, only these tools are active) */\n\tenabled_tools?: string[];\n\t/** Tool names explicitly blocked in this mode regardless of enabled_tools */\n\tdenied_tools?: string[];\n\t/** Allowed write paths in this mode (glob patterns, only applies if write/edit is enabled) */\n\tallowed_write_paths?: string[];\n\t/** Regex patterns for allowed bash commands. If set, a command must match at least one to execute. */\n\tallowed_bash_commands?: string[];\n\t/** Regex patterns for denied bash commands. A command matching any pattern is blocked. */\n\tdenied_bash_commands?: string[];\n}\n\n/**\n * Tool-outcome-driven thinking escalation (on by default).\n *\n * The default (fast) path keeps thinking low so mechanical tool turns — reads,\n * greps, successful edits — don't pay the extended-thinking prefill tax. When a\n * tool *fails*, the next turn(s) escalate to a higher thinking level so the model\n * reasons through the failure, then the level is restored. This buys low latency\n * on the happy path while preserving deep reasoning exactly when something breaks.\n *\n * Enabled by default; set `thinking_escalation.enabled` to `false` to turn it off.\n *\n * Note: escalation uses the same setter as manual thinking control, so the\n * escalated level is briefly written to settings and restored when the window\n * ends. If a run is interrupted mid-window, the escalated level may persist until\n * the next change.\n */\nexport interface ThinkingEscalationConfig {\n\t/** Master switch. Default: true (set to false to disable). */\n\tenabled?: boolean;\n\t/** Level to escalate to after a tool error. Default: \"high\". */\n\ton_error?: ThinkingLevel;\n\t/** Restrict escalation to errors from these tool names. Default: any tool. */\n\ttools?: string[];\n\t/** Number of subsequent turns to stay escalated after an error. Default: 1. */\n\tcooldown_turns?: number;\n}\n\n/** LLM defaults seeded in hoo-config.json and honoured during model selection. */\nexport interface HooLlmConfig {\n\t/** Preferred provider when the pi-layer settings.json has no saved default. */\n\tdefault_provider?: string;\n\t/** Preferred model id for `default_provider` (otherwise the provider's built-in default). */\n\tdefault_model?: string;\n\t/** Informational map of provider → env var holding its API key. */\n\tproviders?: Record<string, { api_key_env?: string }>;\n}\n\nexport interface HooConfig {\n\t/** LLM default provider/model preferences. */\n\tllm?: HooLlmConfig;\n\t/** Manually-pinned active mode (overrides default \"build\") */\n\tactive_mode?: string;\n\t/** Per-mode configuration keyed by mode name */\n\tmodes?: Record<string, ModeConfig>;\n\t/** Extra directories to search for `{name}/system.md` mode files (after project + user). */\n\tmode_paths?: string[];\n\t/** Raise thinking after tool failures, restore on success. On by default; set `enabled: false` to disable. */\n\tthinking_escalation?: ThinkingEscalationConfig;\n}\n\nexport function readConfig(): HooConfig {\n\ttry {\n\t\treturn JSON.parse(readFileSync(GLOBAL_CONFIG_PATH, \"utf8\")) as HooConfig;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport function writeConfig(config: HooConfig): void {\n\tif (!existsSync(HOOCODE_DIR)) mkdirSync(HOOCODE_DIR, { recursive: true });\n\twriteFileSync(GLOBAL_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\\n`, \"utf8\");\n}\n\n/**\n * Deep-merges a project-local config on top of the global config.\n *\n * Merge rules:\n * - active_mode: project wins if set\n * - modes[x].auto_allow: union of global + project arrays\n * - modes[x].allowed_write_paths: union of global + project arrays\n * - modes[x].enabled_tools: project wins if set, else falls back to global\n * - mode_paths: project list is prepended so project paths are searched first\n * - thinking_escalation: project wins as a whole if set, else inherit global\n */\nexport function mergeConfigs(global: HooConfig, project: HooConfig): HooConfig {\n\tconst merged: HooConfig = { ...global };\n\n\tif (project.active_mode !== undefined) merged.active_mode = project.active_mode;\n\n\t// thinking_escalation: project wins as a whole if set, else inherit global.\n\tif (project.thinking_escalation !== undefined) merged.thinking_escalation = project.thinking_escalation;\n\n\tif (project.modes) {\n\t\tmerged.modes = { ...(global.modes ?? {}) };\n\t\tfor (const [mode, projectCfg] of Object.entries(project.modes)) {\n\t\t\tconst globalCfg = global.modes?.[mode] ?? {};\n\t\t\tmerged.modes[mode] = {\n\t\t\t\t...globalCfg,\n\t\t\t\t...projectCfg,\n\t\t\t\t// Union both auto_allow lists so project can extend, not just replace\n\t\t\t\tauto_allow: Array.from(new Set([...(globalCfg.auto_allow ?? []), ...(projectCfg.auto_allow ?? [])])),\n\t\t\t\t// Union allowed_write_paths so project can extend\n\t\t\t\tallowed_write_paths: Array.from(\n\t\t\t\t\tnew Set([...(globalCfg.allowed_write_paths ?? []), ...(projectCfg.allowed_write_paths ?? [])]),\n\t\t\t\t),\n\t\t\t\t// enabled_tools: project wins if set, else falls back to global\n\t\t\t\tenabled_tools: projectCfg.enabled_tools ?? globalCfg.enabled_tools,\n\t\t\t\t// denied_tools: union so project can add more denied tools on top of global\n\t\t\t\tdenied_tools: Array.from(new Set([...(globalCfg.denied_tools ?? []), ...(projectCfg.denied_tools ?? [])])),\n\t\t\t\t// allowed_bash_commands: project wins if set, else falls back to global\n\t\t\t\tallowed_bash_commands: projectCfg.allowed_bash_commands ?? globalCfg.allowed_bash_commands,\n\t\t\t\t// denied_bash_commands: union so project can add more denied patterns on top of global\n\t\t\t\tdenied_bash_commands: Array.from(\n\t\t\t\t\tnew Set([...(globalCfg.denied_bash_commands ?? []), ...(projectCfg.denied_bash_commands ?? [])]),\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t}\n\n\tif (project.mode_paths || global.mode_paths) {\n\t\t// Project paths first so they're searched before global paths\n\t\tmerged.mode_paths = dedupePaths([...(project.mode_paths ?? []), ...(global.mode_paths ?? [])]);\n\t}\n\n\treturn merged;\n}\n\nfunction dedupePaths(paths: string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst out: string[] = [];\n\tfor (const p of paths) {\n\t\tif (!seen.has(p)) {\n\t\t\tseen.add(p);\n\t\t\tout.push(p);\n\t\t}\n\t}\n\treturn out;\n}\n\nexport function mergeSearchPaths(...sources: (string[] | undefined)[]): string[] {\n\tconst merged: string[] = [];\n\tfor (const source of sources) {\n\t\tif (!source) continue;\n\t\tmerged.push(...source);\n\t}\n\treturn dedupePaths(merged);\n}\n\n/**\n * Reads the global config and optionally overlays the project-local config at\n * `./.hoocode/hoo-config.json`. Project values win on all scalar fields; arrays are\n * unioned (see mergeConfigs for full rules).\n */\nexport function readMergedConfig(cwd: string): HooConfig {\n\tconst global = readConfig();\n\tconst projectPath = join(cwd, \".hoocode\", \"hoo-config.json\");\n\tif (!existsSync(projectPath)) return global;\n\ttry {\n\t\tconst project = JSON.parse(readFileSync(projectPath, \"utf8\")) as HooConfig;\n\t\treturn mergeConfigs(global, project);\n\t} catch {\n\t\treturn global;\n\t}\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/extensions/core/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hoo-config.json — types, I/O, and merge rules shared by the hoo-core extensions.\n *\n * Config merge order (lowest → highest priority):\n * 1. ~/.hoocode/hoo-config.json (global defaults)\n * 2. ./.hoocode/hoo-config.json (project overrides — scalars win; arrays union)\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport { getHooCodeDir } from \"../../config.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\nconst GLOBAL_CONFIG_PATH = join(HOOCODE_DIR, \"hoo-config.json\");\n\ninterface ModeConfig {\n\t/** Tool names that bypass the permission gate in this mode */\n\tauto_allow?: string[];\n\t/** Tool names available in this mode (if set, only these tools are active) */\n\tenabled_tools?: string[];\n\t/** Tool names explicitly blocked in this mode regardless of enabled_tools */\n\tdenied_tools?: string[];\n\t/** Allowed write paths in this mode (glob patterns, only applies if write/edit is enabled) */\n\tallowed_write_paths?: string[];\n\t/** Regex patterns for allowed bash commands. If set, a command must match at least one to execute. */\n\tallowed_bash_commands?: string[];\n\t/** Regex patterns for denied bash commands. A command matching any pattern is blocked. */\n\tdenied_bash_commands?: string[];\n}\n\n/**\n * Tool-outcome-driven thinking escalation (on by default).\n *\n * The default (fast) path keeps thinking low so mechanical tool turns — reads,\n * greps, successful edits — don't pay the extended-thinking prefill tax. When a\n * tool *fails*, the next turn(s) escalate to a higher thinking level so the model\n * reasons through the failure, then the level is restored. This buys low latency\n * on the happy path while preserving deep reasoning exactly when something breaks.\n *\n * Enabled by default; set `thinking_escalation.enabled` to `false` to turn it off.\n *\n * Note: escalation uses the same setter as manual thinking control, so the\n * escalated level is briefly written to settings and restored when the window\n * ends. If a run is interrupted mid-window, the escalated level may persist until\n * the next change.\n */\ninterface ThinkingEscalationConfig {\n\t/** Master switch. Default: true (set to false to disable). */\n\tenabled?: boolean;\n\t/** Level to escalate to after a tool error. Default: \"high\". */\n\ton_error?: ThinkingLevel;\n\t/** Restrict escalation to errors from these tool names. Default: any tool. */\n\ttools?: string[];\n\t/** Number of subsequent turns to stay escalated after an error. Default: 1. */\n\tcooldown_turns?: number;\n}\n\n/** LLM defaults seeded in hoo-config.json and honoured during model selection. */\ninterface HooLlmConfig {\n\t/** Preferred provider when the pi-layer settings.json has no saved default. */\n\tdefault_provider?: string;\n\t/** Preferred model id for `default_provider` (otherwise the provider's built-in default). */\n\tdefault_model?: string;\n\t/** Informational map of provider → env var holding its API key. */\n\tproviders?: Record<string, { api_key_env?: string }>;\n}\n\nexport interface HooConfig {\n\t/** LLM default provider/model preferences. */\n\tllm?: HooLlmConfig;\n\t/** Manually-pinned active mode (overrides default \"build\") */\n\tactive_mode?: string;\n\t/** Per-mode configuration keyed by mode name */\n\tmodes?: Record<string, ModeConfig>;\n\t/** Extra directories to search for `{name}/system.md` mode files (after project + user). */\n\tmode_paths?: string[];\n\t/** Raise thinking after tool failures, restore on success. On by default; set `enabled: false` to disable. */\n\tthinking_escalation?: ThinkingEscalationConfig;\n}\n\nexport function readConfig(): HooConfig {\n\ttry {\n\t\treturn JSON.parse(readFileSync(GLOBAL_CONFIG_PATH, \"utf8\")) as HooConfig;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport function writeConfig(config: HooConfig): void {\n\tif (!existsSync(HOOCODE_DIR)) mkdirSync(HOOCODE_DIR, { recursive: true });\n\twriteFileSync(GLOBAL_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\\n`, \"utf8\");\n}\n\n/**\n * Deep-merges a project-local config on top of the global config.\n *\n * Merge rules:\n * - active_mode: project wins if set\n * - modes[x].auto_allow: union of global + project arrays\n * - modes[x].allowed_write_paths: union of global + project arrays\n * - modes[x].enabled_tools: project wins if set, else falls back to global\n * - mode_paths: project list is prepended so project paths are searched first\n * - thinking_escalation: project wins as a whole if set, else inherit global\n */\nexport function mergeConfigs(global: HooConfig, project: HooConfig): HooConfig {\n\tconst merged: HooConfig = { ...global };\n\n\tif (project.active_mode !== undefined) merged.active_mode = project.active_mode;\n\n\t// thinking_escalation: project wins as a whole if set, else inherit global.\n\tif (project.thinking_escalation !== undefined) merged.thinking_escalation = project.thinking_escalation;\n\n\tif (project.modes) {\n\t\tmerged.modes = { ...(global.modes ?? {}) };\n\t\tfor (const [mode, projectCfg] of Object.entries(project.modes)) {\n\t\t\tconst globalCfg = global.modes?.[mode] ?? {};\n\t\t\tmerged.modes[mode] = {\n\t\t\t\t...globalCfg,\n\t\t\t\t...projectCfg,\n\t\t\t\t// Union both auto_allow lists so project can extend, not just replace\n\t\t\t\tauto_allow: Array.from(new Set([...(globalCfg.auto_allow ?? []), ...(projectCfg.auto_allow ?? [])])),\n\t\t\t\t// Union allowed_write_paths so project can extend\n\t\t\t\tallowed_write_paths: Array.from(\n\t\t\t\t\tnew Set([...(globalCfg.allowed_write_paths ?? []), ...(projectCfg.allowed_write_paths ?? [])]),\n\t\t\t\t),\n\t\t\t\t// enabled_tools: project wins if set, else falls back to global\n\t\t\t\tenabled_tools: projectCfg.enabled_tools ?? globalCfg.enabled_tools,\n\t\t\t\t// denied_tools: union so project can add more denied tools on top of global\n\t\t\t\tdenied_tools: Array.from(new Set([...(globalCfg.denied_tools ?? []), ...(projectCfg.denied_tools ?? [])])),\n\t\t\t\t// allowed_bash_commands: project wins if set, else falls back to global\n\t\t\t\tallowed_bash_commands: projectCfg.allowed_bash_commands ?? globalCfg.allowed_bash_commands,\n\t\t\t\t// denied_bash_commands: union so project can add more denied patterns on top of global\n\t\t\t\tdenied_bash_commands: Array.from(\n\t\t\t\t\tnew Set([...(globalCfg.denied_bash_commands ?? []), ...(projectCfg.denied_bash_commands ?? [])]),\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t}\n\n\tif (project.mode_paths || global.mode_paths) {\n\t\t// Project paths first so they're searched before global paths\n\t\tmerged.mode_paths = dedupePaths([...(project.mode_paths ?? []), ...(global.mode_paths ?? [])]);\n\t}\n\n\treturn merged;\n}\n\nfunction dedupePaths(paths: string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst out: string[] = [];\n\tfor (const p of paths) {\n\t\tif (!seen.has(p)) {\n\t\t\tseen.add(p);\n\t\t\tout.push(p);\n\t\t}\n\t}\n\treturn out;\n}\n\nexport function mergeSearchPaths(...sources: (string[] | undefined)[]): string[] {\n\tconst merged: string[] = [];\n\tfor (const source of sources) {\n\t\tif (!source) continue;\n\t\tmerged.push(...source);\n\t}\n\treturn dedupePaths(merged);\n}\n\n/**\n * Reads the global config and optionally overlays the project-local config at\n * `./.hoocode/hoo-config.json`. Project values win on all scalar fields; arrays are\n * unioned (see mergeConfigs for full rules).\n */\nexport function readMergedConfig(cwd: string): HooConfig {\n\tconst global = readConfig();\n\tconst projectPath = join(cwd, \".hoocode\", \"hoo-config.json\");\n\tif (!existsSync(projectPath)) return global;\n\ttry {\n\t\tconst project = JSON.parse(readFileSync(projectPath, \"utf8\")) as HooConfig;\n\t\treturn mergeConfigs(global, project);\n\t} catch {\n\t\treturn global;\n\t}\n}\n"]}
@@ -12,23 +12,5 @@
12
12
  * 3. MCP servers registered by plugins/extensions during load
13
13
  */
14
14
  import type { ExtensionAPI } from "../../core/extensions/types.js";
15
- export interface McpServerConfig {
16
- /** Unique server identifier used as prefix for registered tool names */
17
- name: string;
18
- /** Executable to spawn (stdio transport). One of command/url is required. */
19
- command?: string;
20
- /** Optional arguments passed to the command */
21
- args?: string[];
22
- /** Optional extra environment variables for the server process */
23
- env?: Record<string, string>;
24
- /** Transport: "stdio" (default with command), "http" (Streamable HTTP), or "sse" (legacy) */
25
- type?: "stdio" | "http" | "sse";
26
- /** Remote server URL (http/sse transports) */
27
- url?: string;
28
- /** Extra HTTP headers (e.g. Authorization) for remote transports */
29
- headers?: Record<string, string>;
30
- /** Run MCP tools in background by default (default: true for MCP servers) */
31
- background?: boolean;
32
- }
33
15
  export declare function setupMcpLoader(pi: ExtensionAPI): void;
34
16
  //# sourceMappingURL=mcp-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-loader.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/mcp-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,OAAO,KAAK,EAAE,YAAY,EAAuD,MAAM,gCAAgC,CAAC;AAuBxH,MAAM,WAAW,eAAe;IAC/B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,6FAA6F;IAC7F,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;IAChC,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAiZD,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAqOrD","sourcesContent":["/**\n * MCP server loader — discovers server configs (standard mcp.json locations,\n * hoocode's per-server JSON files, plugin registrations), connects via JSON-RPC\n * 2.0 over stdio (`command`), Streamable HTTP (`{ \"type\": \"http\", \"url\": ... }`),\n * or legacy SSE (`\"type\": \"sse\"`), and registers each server tool as\n * `mcp_<server>_<tool>`.\n *\n * Config sources (first-wins by server name):\n * 1. ~/.agents/mcp.json (user), ./.agents/mcp.json (project),\n * ~/.config/claude/mcp.json (Claude Desktop)\n * 2. ~/.hoocode/mcp-servers/*.json and ./.hoocode/mcp-servers/*.json\n * 3. MCP servers registered by plugins/extensions during load\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentToolResult, AgentToolUpdateCallback, McpRemoteOptions } from \"@kolisachint/hoocode-agent-core\";\nimport { connectHttpMcpServer, summarizeArgs } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { getHooCodeDir } from \"../../config.js\";\nimport { getExtensionMcpServers } from \"../../core/extension-mcp-servers.js\";\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent, ToolDefinition } from \"../../core/extensions/types.js\";\nimport { formatDurationSecs } from \"../../core/format-duration.js\";\nimport { clearMcpServerStatuses, setMcpServerStatus } from \"../../core/mcp-status.js\";\nimport { deferMcpSchemas, subagentSkipMcp } from \"../../core/subagent-depth.js\";\nimport { taskStore } from \"../../core/task-store.js\";\nimport { type DeferredMcpToolEntry, formatDeferredCatalog, selectResolvable } from \"./mcp-deferred.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\n\ninterface McpToolDetails {\n\telapsed?: number;\n}\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn (stdio transport). One of command/url is required. */\n\tcommand?: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n\t/** Transport: \"stdio\" (default with command), \"http\" (Streamable HTTP), or \"sse\" (legacy) */\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\t/** Remote server URL (http/sse transports) */\n\turl?: string;\n\t/** Extra HTTP headers (e.g. Authorization) for remote transports */\n\theaders?: Record<string, string>;\n\t/** Run MCP tools in background by default (default: true for MCP servers) */\n\tbackground?: boolean;\n}\n\n/** Standard MCP config format used by Claude Desktop, Claude Code, VS Code / Copilot */\ninterface StandardMcpServerConfig {\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\turl?: string;\n\theaders?: Record<string, string>;\n\tbackground?: boolean;\n}\n\ninterface StandardMcpConfig {\n\tmcpServers?: Record<string, StandardMcpServerConfig>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;\n\t/** Send a JSON-RPC notification (no id, no response expected). */\n\tnotify(method: string, params?: unknown): void;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n/**\n * Server configs retained by name so a tool call can transparently reconnect a\n * dropped server (process churn between turns, server exit, a racing teardown)\n * instead of permanently failing with \"not connected\".\n */\nconst mcpServerConfigs = new Map<string, McpServerConfig>();\n/** Remote-transport options (OAuth storage, auth callbacks) per server name. */\nconst mcpRemoteOptions = new Map<string, McpRemoteOptions>();\n/**\n * Servers with a browser authorization flow in flight. While a name is here,\n * a failed handshake must NOT tear the connection down (that would kill the\n * OAuth loopback listener); the auth-completion handler reconnects instead.\n */\nconst mcpAuthPending = new Set<string>();\n\n/** Timeout for the connection handshake (initialize / tools/list). Tool calls\n * themselves are left untimed since MCP tools can be long-running. */\nconst MCP_HANDSHAKE_TIMEOUT_MS = 15000;\n\nfunction spawnMcpServer(config: McpServerConfig & { command: string }): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tif (timeoutMs && timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (pending.delete(id)) {\n\t\t\t\t\t\treject(new Error(`MCP server \"${config.name}\" timed out after ${timeoutMs}ms on ${method}`));\n\t\t\t\t\t}\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimer.unref?.();\n\t\t\t}\n\t\t\tpending.set(id, {\n\t\t\t\tresolve: (r) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tresolve(r);\n\t\t\t\t},\n\t\t\t\treject: (e) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\treject(e);\n\t\t\t\t},\n\t\t\t});\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\tfunction notify(method: string, params?: unknown): void {\n\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n\t}\n\n\treturn {\n\t\trpc,\n\t\tnotify,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\n/** Remote servers use `{ type: \"http\" | \"sse\", url }`; stdio servers use `command`. */\nfunction isRemoteServer(config: McpServerConfig): boolean {\n\treturn config.type === \"http\" || config.type === \"sse\" || (!config.command && typeof config.url === \"string\");\n}\n\nfunction openMcpConnection(config: McpServerConfig): McpConnection {\n\tif (isRemoteServer(config)) {\n\t\tif (typeof config.url !== \"string\") {\n\t\t\tthrow new Error(`MCP server \"${config.name}\" has type \"${config.type}\" but no \"url\"`);\n\t\t}\n\t\treturn connectHttpMcpServer(\n\t\t\t{\n\t\t\t\tname: config.name,\n\t\t\t\turl: config.url,\n\t\t\t\theaders: config.headers,\n\t\t\t\ttype: config.type === \"sse\" ? \"sse\" : \"http\",\n\t\t\t},\n\t\t\tmcpRemoteOptions.get(config.name),\n\t\t);\n\t}\n\tif (typeof config.command !== \"string\") {\n\t\tthrow new Error(`MCP server \"${config.name}\" needs a \"command\" (stdio) or a \"url\" (http/sse)`);\n\t}\n\treturn spawnMcpServer(config as McpServerConfig & { command: string });\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = openMcpConnection(config);\n\tmcpConnections.set(config.name, conn);\n\n\ttry {\n\t\tawait conn.rpc(\n\t\t\t\"initialize\",\n\t\t\t{\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: { tools: {} },\n\t\t\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t\t\t},\n\t\t\tMCP_HANDSHAKE_TIMEOUT_MS,\n\t\t);\n\n\t\t// Per the MCP spec the client must acknowledge a successful initialize with the\n\t\t// initialized notification before issuing further requests; strict servers gate\n\t\t// tools/call on it.\n\t\tconn.notify(\"notifications/initialized\");\n\n\t\tconst toolsResult = (await conn.rpc(\"tools/list\", {}, MCP_HANDSHAKE_TIMEOUT_MS)) as {\n\t\t\ttools?: McpToolDef[];\n\t\t};\n\t\treturn { conn, tools: toolsResult.tools ?? [] };\n\t} catch (error) {\n\t\t// Don't leave a half-connected entry behind: remote transports have no\n\t\t// process-exit event to evict them, so getOrConnectMcp would keep handing\n\t\t// out the dead connection instead of reconnecting. Exception: a pending\n\t\t// browser authorization — terminating would kill the OAuth loopback\n\t\t// listener; the auth-completion handler reconnects that server itself.\n\t\tif (!mcpAuthPending.has(config.name)) {\n\t\t\tconn.terminate();\n\t\t\tif (mcpConnections.get(config.name) === conn) mcpConnections.delete(config.name);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Return a live connection for a server, lazily reconnecting from the retained\n * config when the previous connection was torn down. Returns undefined only when\n * no config is known or a fresh connect attempt fails.\n */\nasync function getOrConnectMcp(name: string): Promise<McpConnection | undefined> {\n\tconst existing = mcpConnections.get(name);\n\tif (existing) return existing;\n\tconst config = mcpServerConfigs.get(name);\n\tif (!config) return undefined;\n\ttry {\n\t\tconst { conn } = await connectMcpServer(config);\n\t\treturn conn;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nlet mcpExitCleanupInstalled = false;\n/** Kill spawned MCP servers when the host process exits so they don't linger as\n * orphans (their stdin merely goes idle, which doesn't terminate them). */\nfunction installMcpExitCleanup(): void {\n\tif (mcpExitCleanupInstalled) return;\n\tmcpExitCleanupInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const conn of mcpConnections.values()) {\n\t\t\ttry {\n\t\t\t\tconn.terminate();\n\t\t\t} catch {\n\t\t\t\t// best-effort cleanup\n\t\t\t}\n\t\t}\n\t\tmcpConnections.clear();\n\t});\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\n/**\n * Parse standard MCP config format (used by Claude Desktop, VS Code, etc.)\n * into hoocode's McpServerConfig format.\n */\nfunction parseStandardMcpConfig(config: StandardMcpConfig, _source: string): McpServerConfig[] {\n\tif (!config.mcpServers) return [];\n\n\tconst servers: McpServerConfig[] = [];\n\tfor (const [name, serverConfig] of Object.entries(config.mcpServers)) {\n\t\tservers.push({\n\t\t\tname,\n\t\t\tcommand: serverConfig.command,\n\t\t\targs: serverConfig.args,\n\t\t\tenv: serverConfig.env,\n\t\t\ttype: serverConfig.type,\n\t\t\turl: serverConfig.url,\n\t\t\theaders: serverConfig.headers,\n\t\t\tbackground: serverConfig.background,\n\t\t});\n\t}\n\treturn servers;\n}\n\n/**\n * Load MCP servers from a standard mcp.json file.\n * Returns an array of McpServerConfig, or empty array if file doesn't exist or is invalid.\n */\nfunction loadStandardMcpFile(filePath: string): McpServerConfig[] {\n\tif (!existsSync(filePath)) return [];\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf8\");\n\t\tconst config = JSON.parse(content) as StandardMcpConfig;\n\t\treturn parseStandardMcpConfig(config, filePath);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Build the full {@link ToolDefinition} for one MCP tool — the complete JSON\n * schema plus the connect/execute machinery. Shared by the eager path (register\n * every tool up front) and the deferred path (materialize on resolve), so both\n * produce identical, callable tools.\n */\nfunction buildMcpToolDefinition(serverConfig: McpServerConfig, tool: McpToolDef): ToolDefinition {\n\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\tconst schema = buildMcpSchema(tool);\n\tconst capturedServer = serverConfig.name;\n\tconst capturedTool = tool.name;\n\t// MCP tools default to background mode since they are external processes with potential high latency\n\tconst isBackground = serverConfig.background !== false;\n\n\treturn {\n\t\tname: toolName,\n\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\tdescription: tool.description,\n\t\tparameters: schema,\n\t\tbackground: isBackground,\n\t\t// Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —\n\t\t// parallel to the subagent `Task [type] <desc>` line. Without this the\n\t\t// ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.\n\t\t// The args summary reuses the same helper as the background start/finish\n\t\t// messages so the chat title stays in sync with them.\n\t\trenderCall(args, theme) {\n\t\t\tconst summary = summarizeArgs((args ?? {}) as Record<string, unknown>);\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"MCP \")) +\n\t\t\t\ttheme.fg(\"mcp\", `[${capturedServer} › ${capturedTool}]`) +\n\t\t\t\t(summary ? theme.fg(\"dim\", ` ${summary}`) : \"\");\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t\tasync execute(\n\t\t\t_toolCallId: string,\n\t\t\tparams: Static<typeof schema>,\n\t\t\tsignal: AbortSignal,\n\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\tconst startTime = Date.now();\n\t\t\t// Background MCP tools get a task store entry so they appear in the task pane.\n\t\t\t// Foreground tools skip this (their result is awaited inline). The server\n\t\t\t// name rides in subagentMode and becomes the row's `[server]` origin tag;\n\t\t\t// the title carries just the tool.\n\t\t\tconst task = isBackground\n\t\t\t\t? taskStore.create(capturedTool, { source: \"mcp\", subagentMode: capturedServer })\n\t\t\t\t: undefined;\n\t\t\tif (task) taskStore.update(task.id, { status: \"in_progress\" });\n\n\t\t\t// Lazily (re)connect: a dropped connection (server exit, process churn\n\t\t\t// between turns, a racing teardown) should transparently reconnect from\n\t\t\t// the retained config rather than permanently fail with \"not connected\".\n\t\t\tconst activeConn = await getOrConnectMcp(capturedServer);\n\t\t\tif (!activeConn) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: `MCP server \"${capturedServer}\" is not connected (reconnect attempt failed)` },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t});\n\n\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\tactiveConn.rpc(\"tools/call\", { name: capturedTool, arguments: params }),\n\t\t\t\t\tabortPromise,\n\t\t\t\t]);\n\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"done\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = result.content\n\t\t\t\t.map((c) => (c.type === \"text\" ? c.text : \"\"))\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (!text) return new Text(\"\", 0, 0);\n\n\t\t\tconst details = result.details as McpToolDetails | undefined;\n\t\t\tconst elapsed = details?.elapsed;\n\t\t\tconst elapsedText = elapsed ? ` ${formatDurationSecs(elapsed / 1000)}` : \"\";\n\t\t\tconst identity = `${capturedServer} › ${capturedTool}`;\n\n\t\t\t// Use context.isError to determine status (framework sets this when tools throw)\n\t\t\tconst isError = context.isError;\n\t\t\tconst statusGlyph = isError ? \"✗\" : \"✓\";\n\t\t\tconst statusLabel = isError ? \"failed\" : \"done\";\n\t\t\tconst statusColor = isError ? \"error\" : \"success\";\n\n\t\t\tconst spine = (s: string) => theme.fg(\"borderMuted\", s);\n\t\t\tconst header =\n\t\t\t\t`${spine(\"╭\")} ${theme.fg(statusColor, statusGlyph)} ` +\n\t\t\t\t`${theme.bold(theme.fg(statusColor, statusLabel))} ` +\n\t\t\t\t`${theme.fg(\"mcp\", identity)}${elapsedText}`;\n\t\t\tconst body = text\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => `${spine(\"│\")} ${theme.fg(\"toolOutput\", line)}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\treturn new Text(`${header}\\n${body}\\n${spine(\"╰\")}`, 0, 0);\n\t\t},\n\t} as ToolDefinition;\n}\n\nconst RESOLVE_MCP_TOOLS_NAME = \"ResolveMcpTools\";\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// A spawned subagent whose tool allowlist has no MCP tools is told by its\n\t\t// parent to skip server connection entirely (see SUBAGENT_SKIP_MCP_ENV).\n\t\t// Each connect is a ~15s-timeout handshake; doing it for a subagent that can\n\t\t// never call the tools is pure startup latency.\n\t\tif (subagentSkipMcp()) return;\n\n\t\tinstallMcpExitCleanup();\n\t\t// Rebuild the status registry from scratch: a reload or session switch runs\n\t\t// this pass again and the startup summary must not double-count.\n\t\tclearMcpServerStatuses();\n\t\tconst allServerConfigs: McpServerConfig[] = [];\n\t\tconst seenNames = new Set<string>();\n\n\t\t// 1. Load from standard mcp.json locations\n\t\t// User-level: ~/.agents/mcp.json\n\t\tconst userAgentsConfig = loadStandardMcpFile(join(homedir(), \".agents\", \"mcp.json\"));\n\t\tfor (const config of userAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Project-level: ./.agents/mcp.json\n\t\tconst projectAgentsConfig = loadStandardMcpFile(join(ctx.cwd, \".agents\", \"mcp.json\"));\n\t\tfor (const config of projectAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Claude Desktop: ~/.config/claude/mcp.json\n\t\tconst claudeDesktopConfig = loadStandardMcpFile(join(homedir(), \".config\", \"claude\", \"mcp.json\"));\n\t\tfor (const config of claudeDesktopConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// 2. Load from hoocode's per-server format (existing behavior)\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || (!serverConfig.command && !serverConfig.url)) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"/\"url\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Skip if already loaded from standard config\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// 2b. Load from plugins/extensions that registered MCP servers during load.\n\t\tfor (const entry of getExtensionMcpServers()) {\n\t\t\tfor (const serverConfig of parseStandardMcpConfig(\n\t\t\t\t{ mcpServers: entry.mcpServers },\n\t\t\t\t`plugin:${entry.source}`,\n\t\t\t)) {\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// Deferral (spec §2): inject MCP tool names only and materialize each schema\n\t\t// on demand via ResolveMcpTools. Default-on and top-level only — a subagent that\n\t\t// needs MCP has this env cleared, so it eager-registers its allowlisted tools\n\t\t// at dispatch (the dispatch ↔ schema interaction) and they are immediately callable.\n\t\tconst defer = deferMcpSchemas();\n\t\tconst deferredCatalog: DeferredMcpToolEntry[] = [];\n\t\t// Retain each deferred tool's raw definition + config so ResolveMcpTools can\n\t\t// build the full ToolDefinition on request.\n\t\tconst deferredByName = new Map<string, { serverConfig: McpServerConfig; tool: McpToolDef }>();\n\t\tconst resolvedNames = new Set<string>();\n\n\t\t// Remote servers get OAuth support: tokens persist under\n\t\t// ~/.hoocode/mcp-auth, and when a server demands interactive (browser)\n\t\t// authorization the handshake fails fast while the flow keeps running in\n\t\t// the background — on completion the server is reconnected and its tools\n\t\t// registered (eagerly, since the deferred catalog is already sealed).\n\t\tconst buildRemoteOptions = (serverConfig: McpServerConfig): McpRemoteOptions => ({\n\t\t\tauthStorageDir: join(HOOCODE_DIR, \"mcp-auth\"),\n\t\t\tonAuthRequired: (authorizationUrl, completed) => {\n\t\t\t\tmcpAuthPending.add(serverConfig.name);\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`MCP: \"${serverConfig.name}\" requires authorization — complete the sign-in opened in your browser` +\n\t\t\t\t\t\t(authorizationUrl ? `: ${authorizationUrl}` : \"\"),\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\tcompleted\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\t\t\t\t\t\t\tfor (const tool of tools) pi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\t\t\tdeferred: false,\n\t\t\t\t\t\t\t\tstate: \"connected\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" after authorization (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\t\t\"info\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: failed to connect \"${serverConfig.name}\" after authorization: ${String(err)}`,\n\t\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\tctx.ui.notify(`MCP: authorization for \"${serverConfig.name}\" failed: ${String(err)}`, \"error\");\n\t\t\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\t// 3. Connect to all servers and register (or defer) tools\n\t\tfor (const serverConfig of allServerConfigs) {\n\t\t\t// Retain the config so a tool call can lazily reconnect a dropped server.\n\t\t\tmcpServerConfigs.set(serverConfig.name, serverConfig);\n\t\t\tmcpRemoteOptions.set(serverConfig.name, buildRemoteOptions(serverConfig));\n\t\t\ttry {\n\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\tif (defer) {\n\t\t\t\t\t\tdeferredCatalog.push({ toolName, server: serverConfig.name, description: tool.description });\n\t\t\t\t\t\tdeferredByName.set(toolName, { serverConfig, tool });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A successful connect is reported by the startup resource summary (one\n\t\t\t\t// `mcp` cell plus a details row), not as a transient line above it.\n\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\tdeferred: defer,\n\t\t\t\t\tstate: \"connected\",\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (mcpAuthPending.has(serverConfig.name)) {\n\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\ttoolCount: 0,\n\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\tdeferred: defer,\n\t\t\t\t\t\tstate: \"authorizing\",\n\t\t\t\t\t});\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: \"${serverConfig.name}\" is waiting for browser authorization; tools will register once it completes`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 4. In deferred mode, register the single resolver that materializes schemas on demand.\n\t\tif (defer && deferredCatalog.length > 0) {\n\t\t\tconst resolveParams = Type.Object(\n\t\t\t\t{\n\t\t\t\t\tnames: Type.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"MCP tool names to make callable (e.g. 'mcp_github_create_pr' or 'create_pr').\",\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{ additionalProperties: false },\n\t\t\t);\n\t\t\tpi.registerTool({\n\t\t\t\tname: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tlabel: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tdescription:\n\t\t\t\t\t\"MCP tools are connected but their schemas are loaded on demand to keep context small. Call this with the tool name(s) you need to make them callable, then call the tool(s). Available MCP tools:\\n\" +\n\t\t\t\t\tformatDeferredCatalog(deferredCatalog),\n\t\t\t\tpromptSnippet: \"Resolve deferred MCP tool schemas by name before calling them.\",\n\t\t\t\tparameters: resolveParams,\n\t\t\t\tasync execute(_toolCallId: string, params: Static<typeof resolveParams>) {\n\t\t\t\t\tconst matched = selectResolvable(deferredCatalog, params.names ?? []);\n\t\t\t\t\tconst newlyResolved: string[] = [];\n\t\t\t\t\tfor (const entry of matched) {\n\t\t\t\t\t\tif (resolvedNames.has(entry.toolName)) continue;\n\t\t\t\t\t\tconst raw = deferredByName.get(entry.toolName);\n\t\t\t\t\t\tif (!raw) continue;\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(raw.serverConfig, raw.tool));\n\t\t\t\t\t\tresolvedNames.add(entry.toolName);\n\t\t\t\t\t\tnewlyResolved.push(entry.toolName);\n\t\t\t\t\t}\n\t\t\t\t\tconst text = matched.length\n\t\t\t\t\t\t? `Resolved ${newlyResolved.length} MCP tool(s): ${matched.map((m) => m.toolName).join(\", \")}. They are now callable.`\n\t\t\t\t\t\t: `No MCP tools matched: ${(params.names ?? []).join(\", \") || \"(none)\"}. See ResolveMcpTools for the catalog.`;\n\t\t\t\t\treturn { content: [{ type: \"text\" as const, text }], details: undefined };\n\t\t\t\t},\n\t\t\t} as ToolDefinition);\n\t\t}\n\t});\n}\n"]}
1
+ {"version":3,"file":"mcp-loader.d.ts","sourceRoot":"","sources":["../../../src/extensions/core/mcp-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,OAAO,KAAK,EAAE,YAAY,EAAuD,MAAM,gCAAgC,CAAC;AAybxH,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAqOrD","sourcesContent":["/**\n * MCP server loader — discovers server configs (standard mcp.json locations,\n * hoocode's per-server JSON files, plugin registrations), connects via JSON-RPC\n * 2.0 over stdio (`command`), Streamable HTTP (`{ \"type\": \"http\", \"url\": ... }`),\n * or legacy SSE (`\"type\": \"sse\"`), and registers each server tool as\n * `mcp_<server>_<tool>`.\n *\n * Config sources (first-wins by server name):\n * 1. ~/.agents/mcp.json (user), ./.agents/mcp.json (project),\n * ~/.config/claude/mcp.json (Claude Desktop)\n * 2. ~/.hoocode/mcp-servers/*.json and ./.hoocode/mcp-servers/*.json\n * 3. MCP servers registered by plugins/extensions during load\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentToolResult, AgentToolUpdateCallback, McpRemoteOptions } from \"@kolisachint/hoocode-agent-core\";\nimport { connectHttpMcpServer, summarizeArgs } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { getHooCodeDir } from \"../../config.js\";\nimport { getExtensionMcpServers } from \"../../core/extension-mcp-servers.js\";\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent, ToolDefinition } from \"../../core/extensions/types.js\";\nimport { formatDurationSecs } from \"../../core/format-duration.js\";\nimport { clearMcpServerStatuses, setMcpServerStatus } from \"../../core/mcp-status.js\";\nimport { deferMcpSchemas, subagentSkipMcp } from \"../../core/subagent-depth.js\";\nimport { taskStore } from \"../../core/task-store.js\";\nimport { type DeferredMcpToolEntry, formatDeferredCatalog, selectResolvable } from \"./mcp-deferred.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\n\ninterface McpToolDetails {\n\telapsed?: number;\n}\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\ninterface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn (stdio transport). One of command/url is required. */\n\tcommand?: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n\t/** Transport: \"stdio\" (default with command), \"http\" (Streamable HTTP), or \"sse\" (legacy) */\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\t/** Remote server URL (http/sse transports) */\n\turl?: string;\n\t/** Extra HTTP headers (e.g. Authorization) for remote transports */\n\theaders?: Record<string, string>;\n\t/** Run MCP tools in background by default (default: true for MCP servers) */\n\tbackground?: boolean;\n}\n\n/** Standard MCP config format used by Claude Desktop, Claude Code, VS Code / Copilot */\ninterface StandardMcpServerConfig {\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\turl?: string;\n\theaders?: Record<string, string>;\n\tbackground?: boolean;\n}\n\ninterface StandardMcpConfig {\n\tmcpServers?: Record<string, StandardMcpServerConfig>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;\n\t/** Send a JSON-RPC notification (no id, no response expected). */\n\tnotify(method: string, params?: unknown): void;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n/**\n * Server configs retained by name so a tool call can transparently reconnect a\n * dropped server (process churn between turns, server exit, a racing teardown)\n * instead of permanently failing with \"not connected\".\n */\nconst mcpServerConfigs = new Map<string, McpServerConfig>();\n/** Remote-transport options (OAuth storage, auth callbacks) per server name. */\nconst mcpRemoteOptions = new Map<string, McpRemoteOptions>();\n/**\n * Servers with a browser authorization flow in flight. While a name is here,\n * a failed handshake must NOT tear the connection down (that would kill the\n * OAuth loopback listener); the auth-completion handler reconnects instead.\n */\nconst mcpAuthPending = new Set<string>();\n\n/** Timeout for the connection handshake (initialize / tools/list). Tool calls\n * themselves are left untimed since MCP tools can be long-running. */\nconst MCP_HANDSHAKE_TIMEOUT_MS = 15000;\n\nfunction spawnMcpServer(config: McpServerConfig & { command: string }): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tif (timeoutMs && timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (pending.delete(id)) {\n\t\t\t\t\t\treject(new Error(`MCP server \"${config.name}\" timed out after ${timeoutMs}ms on ${method}`));\n\t\t\t\t\t}\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimer.unref?.();\n\t\t\t}\n\t\t\tpending.set(id, {\n\t\t\t\tresolve: (r) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tresolve(r);\n\t\t\t\t},\n\t\t\t\treject: (e) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\treject(e);\n\t\t\t\t},\n\t\t\t});\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\tfunction notify(method: string, params?: unknown): void {\n\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n\t}\n\n\treturn {\n\t\trpc,\n\t\tnotify,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\n/** Remote servers use `{ type: \"http\" | \"sse\", url }`; stdio servers use `command`. */\nfunction isRemoteServer(config: McpServerConfig): boolean {\n\treturn config.type === \"http\" || config.type === \"sse\" || (!config.command && typeof config.url === \"string\");\n}\n\nfunction openMcpConnection(config: McpServerConfig): McpConnection {\n\tif (isRemoteServer(config)) {\n\t\tif (typeof config.url !== \"string\") {\n\t\t\tthrow new Error(`MCP server \"${config.name}\" has type \"${config.type}\" but no \"url\"`);\n\t\t}\n\t\treturn connectHttpMcpServer(\n\t\t\t{\n\t\t\t\tname: config.name,\n\t\t\t\turl: config.url,\n\t\t\t\theaders: config.headers,\n\t\t\t\ttype: config.type === \"sse\" ? \"sse\" : \"http\",\n\t\t\t},\n\t\t\tmcpRemoteOptions.get(config.name),\n\t\t);\n\t}\n\tif (typeof config.command !== \"string\") {\n\t\tthrow new Error(`MCP server \"${config.name}\" needs a \"command\" (stdio) or a \"url\" (http/sse)`);\n\t}\n\treturn spawnMcpServer(config as McpServerConfig & { command: string });\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = openMcpConnection(config);\n\tmcpConnections.set(config.name, conn);\n\n\ttry {\n\t\tawait conn.rpc(\n\t\t\t\"initialize\",\n\t\t\t{\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: { tools: {} },\n\t\t\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t\t\t},\n\t\t\tMCP_HANDSHAKE_TIMEOUT_MS,\n\t\t);\n\n\t\t// Per the MCP spec the client must acknowledge a successful initialize with the\n\t\t// initialized notification before issuing further requests; strict servers gate\n\t\t// tools/call on it.\n\t\tconn.notify(\"notifications/initialized\");\n\n\t\tconst toolsResult = (await conn.rpc(\"tools/list\", {}, MCP_HANDSHAKE_TIMEOUT_MS)) as {\n\t\t\ttools?: McpToolDef[];\n\t\t};\n\t\treturn { conn, tools: toolsResult.tools ?? [] };\n\t} catch (error) {\n\t\t// Don't leave a half-connected entry behind: remote transports have no\n\t\t// process-exit event to evict them, so getOrConnectMcp would keep handing\n\t\t// out the dead connection instead of reconnecting. Exception: a pending\n\t\t// browser authorization — terminating would kill the OAuth loopback\n\t\t// listener; the auth-completion handler reconnects that server itself.\n\t\tif (!mcpAuthPending.has(config.name)) {\n\t\t\tconn.terminate();\n\t\t\tif (mcpConnections.get(config.name) === conn) mcpConnections.delete(config.name);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Return a live connection for a server, lazily reconnecting from the retained\n * config when the previous connection was torn down. Returns undefined only when\n * no config is known or a fresh connect attempt fails.\n */\nasync function getOrConnectMcp(name: string): Promise<McpConnection | undefined> {\n\tconst existing = mcpConnections.get(name);\n\tif (existing) return existing;\n\tconst config = mcpServerConfigs.get(name);\n\tif (!config) return undefined;\n\ttry {\n\t\tconst { conn } = await connectMcpServer(config);\n\t\treturn conn;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nlet mcpExitCleanupInstalled = false;\n/** Kill spawned MCP servers when the host process exits so they don't linger as\n * orphans (their stdin merely goes idle, which doesn't terminate them). */\nfunction installMcpExitCleanup(): void {\n\tif (mcpExitCleanupInstalled) return;\n\tmcpExitCleanupInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const conn of mcpConnections.values()) {\n\t\t\ttry {\n\t\t\t\tconn.terminate();\n\t\t\t} catch {\n\t\t\t\t// best-effort cleanup\n\t\t\t}\n\t\t}\n\t\tmcpConnections.clear();\n\t});\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\n/**\n * Parse standard MCP config format (used by Claude Desktop, VS Code, etc.)\n * into hoocode's McpServerConfig format.\n */\nfunction parseStandardMcpConfig(config: StandardMcpConfig, _source: string): McpServerConfig[] {\n\tif (!config.mcpServers) return [];\n\n\tconst servers: McpServerConfig[] = [];\n\tfor (const [name, serverConfig] of Object.entries(config.mcpServers)) {\n\t\tservers.push({\n\t\t\tname,\n\t\t\tcommand: serverConfig.command,\n\t\t\targs: serverConfig.args,\n\t\t\tenv: serverConfig.env,\n\t\t\ttype: serverConfig.type,\n\t\t\turl: serverConfig.url,\n\t\t\theaders: serverConfig.headers,\n\t\t\tbackground: serverConfig.background,\n\t\t});\n\t}\n\treturn servers;\n}\n\n/**\n * Load MCP servers from a standard mcp.json file.\n * Returns an array of McpServerConfig, or empty array if file doesn't exist or is invalid.\n */\nfunction loadStandardMcpFile(filePath: string): McpServerConfig[] {\n\tif (!existsSync(filePath)) return [];\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf8\");\n\t\tconst config = JSON.parse(content) as StandardMcpConfig;\n\t\treturn parseStandardMcpConfig(config, filePath);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Build the full {@link ToolDefinition} for one MCP tool — the complete JSON\n * schema plus the connect/execute machinery. Shared by the eager path (register\n * every tool up front) and the deferred path (materialize on resolve), so both\n * produce identical, callable tools.\n */\nfunction buildMcpToolDefinition(serverConfig: McpServerConfig, tool: McpToolDef): ToolDefinition {\n\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\tconst schema = buildMcpSchema(tool);\n\tconst capturedServer = serverConfig.name;\n\tconst capturedTool = tool.name;\n\t// MCP tools default to background mode since they are external processes with potential high latency\n\tconst isBackground = serverConfig.background !== false;\n\n\treturn {\n\t\tname: toolName,\n\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\tdescription: tool.description,\n\t\tparameters: schema,\n\t\tbackground: isBackground,\n\t\t// Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —\n\t\t// parallel to the subagent `Task [type] <desc>` line. Without this the\n\t\t// ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.\n\t\t// The args summary reuses the same helper as the background start/finish\n\t\t// messages so the chat title stays in sync with them.\n\t\trenderCall(args, theme) {\n\t\t\tconst summary = summarizeArgs((args ?? {}) as Record<string, unknown>);\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"MCP \")) +\n\t\t\t\ttheme.fg(\"mcp\", `[${capturedServer} › ${capturedTool}]`) +\n\t\t\t\t(summary ? theme.fg(\"dim\", ` ${summary}`) : \"\");\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t\tasync execute(\n\t\t\t_toolCallId: string,\n\t\t\tparams: Static<typeof schema>,\n\t\t\tsignal: AbortSignal,\n\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\tconst startTime = Date.now();\n\t\t\t// Background MCP tools get a task store entry so they appear in the task pane.\n\t\t\t// Foreground tools skip this (their result is awaited inline). The server\n\t\t\t// name rides in subagentMode and becomes the row's `[server]` origin tag;\n\t\t\t// the title carries just the tool.\n\t\t\tconst task = isBackground\n\t\t\t\t? taskStore.create(capturedTool, { source: \"mcp\", subagentMode: capturedServer })\n\t\t\t\t: undefined;\n\t\t\tif (task) taskStore.update(task.id, { status: \"in_progress\" });\n\n\t\t\t// Lazily (re)connect: a dropped connection (server exit, process churn\n\t\t\t// between turns, a racing teardown) should transparently reconnect from\n\t\t\t// the retained config rather than permanently fail with \"not connected\".\n\t\t\tconst activeConn = await getOrConnectMcp(capturedServer);\n\t\t\tif (!activeConn) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: `MCP server \"${capturedServer}\" is not connected (reconnect attempt failed)` },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t});\n\n\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\tactiveConn.rpc(\"tools/call\", { name: capturedTool, arguments: params }),\n\t\t\t\t\tabortPromise,\n\t\t\t\t]);\n\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"done\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = result.content\n\t\t\t\t.map((c) => (c.type === \"text\" ? c.text : \"\"))\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (!text) return new Text(\"\", 0, 0);\n\n\t\t\tconst details = result.details as McpToolDetails | undefined;\n\t\t\tconst elapsed = details?.elapsed;\n\t\t\tconst elapsedText = elapsed ? ` ${formatDurationSecs(elapsed / 1000)}` : \"\";\n\t\t\tconst identity = `${capturedServer} › ${capturedTool}`;\n\n\t\t\t// Use context.isError to determine status (framework sets this when tools throw)\n\t\t\tconst isError = context.isError;\n\t\t\tconst statusGlyph = isError ? \"✗\" : \"✓\";\n\t\t\tconst statusLabel = isError ? \"failed\" : \"done\";\n\t\t\tconst statusColor = isError ? \"error\" : \"success\";\n\n\t\t\tconst spine = (s: string) => theme.fg(\"borderMuted\", s);\n\t\t\tconst header =\n\t\t\t\t`${spine(\"╭\")} ${theme.fg(statusColor, statusGlyph)} ` +\n\t\t\t\t`${theme.bold(theme.fg(statusColor, statusLabel))} ` +\n\t\t\t\t`${theme.fg(\"mcp\", identity)}${elapsedText}`;\n\t\t\tconst body = text\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => `${spine(\"│\")} ${theme.fg(\"toolOutput\", line)}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\treturn new Text(`${header}\\n${body}\\n${spine(\"╰\")}`, 0, 0);\n\t\t},\n\t} as ToolDefinition;\n}\n\nconst RESOLVE_MCP_TOOLS_NAME = \"ResolveMcpTools\";\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// A spawned subagent whose tool allowlist has no MCP tools is told by its\n\t\t// parent to skip server connection entirely (see SUBAGENT_SKIP_MCP_ENV).\n\t\t// Each connect is a ~15s-timeout handshake; doing it for a subagent that can\n\t\t// never call the tools is pure startup latency.\n\t\tif (subagentSkipMcp()) return;\n\n\t\tinstallMcpExitCleanup();\n\t\t// Rebuild the status registry from scratch: a reload or session switch runs\n\t\t// this pass again and the startup summary must not double-count.\n\t\tclearMcpServerStatuses();\n\t\tconst allServerConfigs: McpServerConfig[] = [];\n\t\tconst seenNames = new Set<string>();\n\n\t\t// 1. Load from standard mcp.json locations\n\t\t// User-level: ~/.agents/mcp.json\n\t\tconst userAgentsConfig = loadStandardMcpFile(join(homedir(), \".agents\", \"mcp.json\"));\n\t\tfor (const config of userAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Project-level: ./.agents/mcp.json\n\t\tconst projectAgentsConfig = loadStandardMcpFile(join(ctx.cwd, \".agents\", \"mcp.json\"));\n\t\tfor (const config of projectAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Claude Desktop: ~/.config/claude/mcp.json\n\t\tconst claudeDesktopConfig = loadStandardMcpFile(join(homedir(), \".config\", \"claude\", \"mcp.json\"));\n\t\tfor (const config of claudeDesktopConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// 2. Load from hoocode's per-server format (existing behavior)\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || (!serverConfig.command && !serverConfig.url)) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"/\"url\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Skip if already loaded from standard config\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// 2b. Load from plugins/extensions that registered MCP servers during load.\n\t\tfor (const entry of getExtensionMcpServers()) {\n\t\t\tfor (const serverConfig of parseStandardMcpConfig(\n\t\t\t\t{ mcpServers: entry.mcpServers },\n\t\t\t\t`plugin:${entry.source}`,\n\t\t\t)) {\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// Deferral (spec §2): inject MCP tool names only and materialize each schema\n\t\t// on demand via ResolveMcpTools. Default-on and top-level only — a subagent that\n\t\t// needs MCP has this env cleared, so it eager-registers its allowlisted tools\n\t\t// at dispatch (the dispatch ↔ schema interaction) and they are immediately callable.\n\t\tconst defer = deferMcpSchemas();\n\t\tconst deferredCatalog: DeferredMcpToolEntry[] = [];\n\t\t// Retain each deferred tool's raw definition + config so ResolveMcpTools can\n\t\t// build the full ToolDefinition on request.\n\t\tconst deferredByName = new Map<string, { serverConfig: McpServerConfig; tool: McpToolDef }>();\n\t\tconst resolvedNames = new Set<string>();\n\n\t\t// Remote servers get OAuth support: tokens persist under\n\t\t// ~/.hoocode/mcp-auth, and when a server demands interactive (browser)\n\t\t// authorization the handshake fails fast while the flow keeps running in\n\t\t// the background — on completion the server is reconnected and its tools\n\t\t// registered (eagerly, since the deferred catalog is already sealed).\n\t\tconst buildRemoteOptions = (serverConfig: McpServerConfig): McpRemoteOptions => ({\n\t\t\tauthStorageDir: join(HOOCODE_DIR, \"mcp-auth\"),\n\t\t\tonAuthRequired: (authorizationUrl, completed) => {\n\t\t\t\tmcpAuthPending.add(serverConfig.name);\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`MCP: \"${serverConfig.name}\" requires authorization — complete the sign-in opened in your browser` +\n\t\t\t\t\t\t(authorizationUrl ? `: ${authorizationUrl}` : \"\"),\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\tcompleted\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\t\t\t\t\t\t\tfor (const tool of tools) pi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\t\t\tdeferred: false,\n\t\t\t\t\t\t\t\tstate: \"connected\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" after authorization (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\t\t\"info\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: failed to connect \"${serverConfig.name}\" after authorization: ${String(err)}`,\n\t\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\tctx.ui.notify(`MCP: authorization for \"${serverConfig.name}\" failed: ${String(err)}`, \"error\");\n\t\t\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\t// 3. Connect to all servers and register (or defer) tools\n\t\tfor (const serverConfig of allServerConfigs) {\n\t\t\t// Retain the config so a tool call can lazily reconnect a dropped server.\n\t\t\tmcpServerConfigs.set(serverConfig.name, serverConfig);\n\t\t\tmcpRemoteOptions.set(serverConfig.name, buildRemoteOptions(serverConfig));\n\t\t\ttry {\n\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\tif (defer) {\n\t\t\t\t\t\tdeferredCatalog.push({ toolName, server: serverConfig.name, description: tool.description });\n\t\t\t\t\t\tdeferredByName.set(toolName, { serverConfig, tool });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A successful connect is reported by the startup resource summary (one\n\t\t\t\t// `mcp` cell plus a details row), not as a transient line above it.\n\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\tdeferred: defer,\n\t\t\t\t\tstate: \"connected\",\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (mcpAuthPending.has(serverConfig.name)) {\n\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\ttoolCount: 0,\n\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\tdeferred: defer,\n\t\t\t\t\t\tstate: \"authorizing\",\n\t\t\t\t\t});\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: \"${serverConfig.name}\" is waiting for browser authorization; tools will register once it completes`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 4. In deferred mode, register the single resolver that materializes schemas on demand.\n\t\tif (defer && deferredCatalog.length > 0) {\n\t\t\tconst resolveParams = Type.Object(\n\t\t\t\t{\n\t\t\t\t\tnames: Type.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"MCP tool names to make callable (e.g. 'mcp_github_create_pr' or 'create_pr').\",\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{ additionalProperties: false },\n\t\t\t);\n\t\t\tpi.registerTool({\n\t\t\t\tname: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tlabel: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tdescription:\n\t\t\t\t\t\"MCP tools are connected but their schemas are loaded on demand to keep context small. Call this with the tool name(s) you need to make them callable, then call the tool(s). Available MCP tools:\\n\" +\n\t\t\t\t\tformatDeferredCatalog(deferredCatalog),\n\t\t\t\tpromptSnippet: \"Resolve deferred MCP tool schemas by name before calling them.\",\n\t\t\t\tparameters: resolveParams,\n\t\t\t\tasync execute(_toolCallId: string, params: Static<typeof resolveParams>) {\n\t\t\t\t\tconst matched = selectResolvable(deferredCatalog, params.names ?? []);\n\t\t\t\t\tconst newlyResolved: string[] = [];\n\t\t\t\t\tfor (const entry of matched) {\n\t\t\t\t\t\tif (resolvedNames.has(entry.toolName)) continue;\n\t\t\t\t\t\tconst raw = deferredByName.get(entry.toolName);\n\t\t\t\t\t\tif (!raw) continue;\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(raw.serverConfig, raw.tool));\n\t\t\t\t\t\tresolvedNames.add(entry.toolName);\n\t\t\t\t\t\tnewlyResolved.push(entry.toolName);\n\t\t\t\t\t}\n\t\t\t\t\tconst text = matched.length\n\t\t\t\t\t\t? `Resolved ${newlyResolved.length} MCP tool(s): ${matched.map((m) => m.toolName).join(\", \")}. They are now callable.`\n\t\t\t\t\t\t: `No MCP tools matched: ${(params.names ?? []).join(\", \") || \"(none)\"}. See ResolveMcpTools for the catalog.`;\n\t\t\t\t\treturn { content: [{ type: \"text\" as const, text }], details: undefined };\n\t\t\t\t},\n\t\t\t} as ToolDefinition);\n\t\t}\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-loader.js","sourceRoot":"","sources":["../../../src/extensions/core/mcp-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP server loader — discovers server configs (standard mcp.json locations,\n * hoocode's per-server JSON files, plugin registrations), connects via JSON-RPC\n * 2.0 over stdio (`command`), Streamable HTTP (`{ \"type\": \"http\", \"url\": ... }`),\n * or legacy SSE (`\"type\": \"sse\"`), and registers each server tool as\n * `mcp_<server>_<tool>`.\n *\n * Config sources (first-wins by server name):\n * 1. ~/.agents/mcp.json (user), ./.agents/mcp.json (project),\n * ~/.config/claude/mcp.json (Claude Desktop)\n * 2. ~/.hoocode/mcp-servers/*.json and ./.hoocode/mcp-servers/*.json\n * 3. MCP servers registered by plugins/extensions during load\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentToolResult, AgentToolUpdateCallback, McpRemoteOptions } from \"@kolisachint/hoocode-agent-core\";\nimport { connectHttpMcpServer, summarizeArgs } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { getHooCodeDir } from \"../../config.js\";\nimport { getExtensionMcpServers } from \"../../core/extension-mcp-servers.js\";\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent, ToolDefinition } from \"../../core/extensions/types.js\";\nimport { formatDurationSecs } from \"../../core/format-duration.js\";\nimport { clearMcpServerStatuses, setMcpServerStatus } from \"../../core/mcp-status.js\";\nimport { deferMcpSchemas, subagentSkipMcp } from \"../../core/subagent-depth.js\";\nimport { taskStore } from \"../../core/task-store.js\";\nimport { type DeferredMcpToolEntry, formatDeferredCatalog, selectResolvable } from \"./mcp-deferred.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\n\ninterface McpToolDetails {\n\telapsed?: number;\n}\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn (stdio transport). One of command/url is required. */\n\tcommand?: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n\t/** Transport: \"stdio\" (default with command), \"http\" (Streamable HTTP), or \"sse\" (legacy) */\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\t/** Remote server URL (http/sse transports) */\n\turl?: string;\n\t/** Extra HTTP headers (e.g. Authorization) for remote transports */\n\theaders?: Record<string, string>;\n\t/** Run MCP tools in background by default (default: true for MCP servers) */\n\tbackground?: boolean;\n}\n\n/** Standard MCP config format used by Claude Desktop, Claude Code, VS Code / Copilot */\ninterface StandardMcpServerConfig {\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\turl?: string;\n\theaders?: Record<string, string>;\n\tbackground?: boolean;\n}\n\ninterface StandardMcpConfig {\n\tmcpServers?: Record<string, StandardMcpServerConfig>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;\n\t/** Send a JSON-RPC notification (no id, no response expected). */\n\tnotify(method: string, params?: unknown): void;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n/**\n * Server configs retained by name so a tool call can transparently reconnect a\n * dropped server (process churn between turns, server exit, a racing teardown)\n * instead of permanently failing with \"not connected\".\n */\nconst mcpServerConfigs = new Map<string, McpServerConfig>();\n/** Remote-transport options (OAuth storage, auth callbacks) per server name. */\nconst mcpRemoteOptions = new Map<string, McpRemoteOptions>();\n/**\n * Servers with a browser authorization flow in flight. While a name is here,\n * a failed handshake must NOT tear the connection down (that would kill the\n * OAuth loopback listener); the auth-completion handler reconnects instead.\n */\nconst mcpAuthPending = new Set<string>();\n\n/** Timeout for the connection handshake (initialize / tools/list). Tool calls\n * themselves are left untimed since MCP tools can be long-running. */\nconst MCP_HANDSHAKE_TIMEOUT_MS = 15000;\n\nfunction spawnMcpServer(config: McpServerConfig & { command: string }): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tif (timeoutMs && timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (pending.delete(id)) {\n\t\t\t\t\t\treject(new Error(`MCP server \"${config.name}\" timed out after ${timeoutMs}ms on ${method}`));\n\t\t\t\t\t}\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimer.unref?.();\n\t\t\t}\n\t\t\tpending.set(id, {\n\t\t\t\tresolve: (r) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tresolve(r);\n\t\t\t\t},\n\t\t\t\treject: (e) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\treject(e);\n\t\t\t\t},\n\t\t\t});\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\tfunction notify(method: string, params?: unknown): void {\n\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n\t}\n\n\treturn {\n\t\trpc,\n\t\tnotify,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\n/** Remote servers use `{ type: \"http\" | \"sse\", url }`; stdio servers use `command`. */\nfunction isRemoteServer(config: McpServerConfig): boolean {\n\treturn config.type === \"http\" || config.type === \"sse\" || (!config.command && typeof config.url === \"string\");\n}\n\nfunction openMcpConnection(config: McpServerConfig): McpConnection {\n\tif (isRemoteServer(config)) {\n\t\tif (typeof config.url !== \"string\") {\n\t\t\tthrow new Error(`MCP server \"${config.name}\" has type \"${config.type}\" but no \"url\"`);\n\t\t}\n\t\treturn connectHttpMcpServer(\n\t\t\t{\n\t\t\t\tname: config.name,\n\t\t\t\turl: config.url,\n\t\t\t\theaders: config.headers,\n\t\t\t\ttype: config.type === \"sse\" ? \"sse\" : \"http\",\n\t\t\t},\n\t\t\tmcpRemoteOptions.get(config.name),\n\t\t);\n\t}\n\tif (typeof config.command !== \"string\") {\n\t\tthrow new Error(`MCP server \"${config.name}\" needs a \"command\" (stdio) or a \"url\" (http/sse)`);\n\t}\n\treturn spawnMcpServer(config as McpServerConfig & { command: string });\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = openMcpConnection(config);\n\tmcpConnections.set(config.name, conn);\n\n\ttry {\n\t\tawait conn.rpc(\n\t\t\t\"initialize\",\n\t\t\t{\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: { tools: {} },\n\t\t\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t\t\t},\n\t\t\tMCP_HANDSHAKE_TIMEOUT_MS,\n\t\t);\n\n\t\t// Per the MCP spec the client must acknowledge a successful initialize with the\n\t\t// initialized notification before issuing further requests; strict servers gate\n\t\t// tools/call on it.\n\t\tconn.notify(\"notifications/initialized\");\n\n\t\tconst toolsResult = (await conn.rpc(\"tools/list\", {}, MCP_HANDSHAKE_TIMEOUT_MS)) as {\n\t\t\ttools?: McpToolDef[];\n\t\t};\n\t\treturn { conn, tools: toolsResult.tools ?? [] };\n\t} catch (error) {\n\t\t// Don't leave a half-connected entry behind: remote transports have no\n\t\t// process-exit event to evict them, so getOrConnectMcp would keep handing\n\t\t// out the dead connection instead of reconnecting. Exception: a pending\n\t\t// browser authorization — terminating would kill the OAuth loopback\n\t\t// listener; the auth-completion handler reconnects that server itself.\n\t\tif (!mcpAuthPending.has(config.name)) {\n\t\t\tconn.terminate();\n\t\t\tif (mcpConnections.get(config.name) === conn) mcpConnections.delete(config.name);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Return a live connection for a server, lazily reconnecting from the retained\n * config when the previous connection was torn down. Returns undefined only when\n * no config is known or a fresh connect attempt fails.\n */\nasync function getOrConnectMcp(name: string): Promise<McpConnection | undefined> {\n\tconst existing = mcpConnections.get(name);\n\tif (existing) return existing;\n\tconst config = mcpServerConfigs.get(name);\n\tif (!config) return undefined;\n\ttry {\n\t\tconst { conn } = await connectMcpServer(config);\n\t\treturn conn;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nlet mcpExitCleanupInstalled = false;\n/** Kill spawned MCP servers when the host process exits so they don't linger as\n * orphans (their stdin merely goes idle, which doesn't terminate them). */\nfunction installMcpExitCleanup(): void {\n\tif (mcpExitCleanupInstalled) return;\n\tmcpExitCleanupInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const conn of mcpConnections.values()) {\n\t\t\ttry {\n\t\t\t\tconn.terminate();\n\t\t\t} catch {\n\t\t\t\t// best-effort cleanup\n\t\t\t}\n\t\t}\n\t\tmcpConnections.clear();\n\t});\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\n/**\n * Parse standard MCP config format (used by Claude Desktop, VS Code, etc.)\n * into hoocode's McpServerConfig format.\n */\nfunction parseStandardMcpConfig(config: StandardMcpConfig, _source: string): McpServerConfig[] {\n\tif (!config.mcpServers) return [];\n\n\tconst servers: McpServerConfig[] = [];\n\tfor (const [name, serverConfig] of Object.entries(config.mcpServers)) {\n\t\tservers.push({\n\t\t\tname,\n\t\t\tcommand: serverConfig.command,\n\t\t\targs: serverConfig.args,\n\t\t\tenv: serverConfig.env,\n\t\t\ttype: serverConfig.type,\n\t\t\turl: serverConfig.url,\n\t\t\theaders: serverConfig.headers,\n\t\t\tbackground: serverConfig.background,\n\t\t});\n\t}\n\treturn servers;\n}\n\n/**\n * Load MCP servers from a standard mcp.json file.\n * Returns an array of McpServerConfig, or empty array if file doesn't exist or is invalid.\n */\nfunction loadStandardMcpFile(filePath: string): McpServerConfig[] {\n\tif (!existsSync(filePath)) return [];\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf8\");\n\t\tconst config = JSON.parse(content) as StandardMcpConfig;\n\t\treturn parseStandardMcpConfig(config, filePath);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Build the full {@link ToolDefinition} for one MCP tool — the complete JSON\n * schema plus the connect/execute machinery. Shared by the eager path (register\n * every tool up front) and the deferred path (materialize on resolve), so both\n * produce identical, callable tools.\n */\nfunction buildMcpToolDefinition(serverConfig: McpServerConfig, tool: McpToolDef): ToolDefinition {\n\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\tconst schema = buildMcpSchema(tool);\n\tconst capturedServer = serverConfig.name;\n\tconst capturedTool = tool.name;\n\t// MCP tools default to background mode since they are external processes with potential high latency\n\tconst isBackground = serverConfig.background !== false;\n\n\treturn {\n\t\tname: toolName,\n\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\tdescription: tool.description,\n\t\tparameters: schema,\n\t\tbackground: isBackground,\n\t\t// Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —\n\t\t// parallel to the subagent `Task [type] <desc>` line. Without this the\n\t\t// ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.\n\t\t// The args summary reuses the same helper as the background start/finish\n\t\t// messages so the chat title stays in sync with them.\n\t\trenderCall(args, theme) {\n\t\t\tconst summary = summarizeArgs((args ?? {}) as Record<string, unknown>);\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"MCP \")) +\n\t\t\t\ttheme.fg(\"mcp\", `[${capturedServer} › ${capturedTool}]`) +\n\t\t\t\t(summary ? theme.fg(\"dim\", ` ${summary}`) : \"\");\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t\tasync execute(\n\t\t\t_toolCallId: string,\n\t\t\tparams: Static<typeof schema>,\n\t\t\tsignal: AbortSignal,\n\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\tconst startTime = Date.now();\n\t\t\t// Background MCP tools get a task store entry so they appear in the task pane.\n\t\t\t// Foreground tools skip this (their result is awaited inline). The server\n\t\t\t// name rides in subagentMode and becomes the row's `[server]` origin tag;\n\t\t\t// the title carries just the tool.\n\t\t\tconst task = isBackground\n\t\t\t\t? taskStore.create(capturedTool, { source: \"mcp\", subagentMode: capturedServer })\n\t\t\t\t: undefined;\n\t\t\tif (task) taskStore.update(task.id, { status: \"in_progress\" });\n\n\t\t\t// Lazily (re)connect: a dropped connection (server exit, process churn\n\t\t\t// between turns, a racing teardown) should transparently reconnect from\n\t\t\t// the retained config rather than permanently fail with \"not connected\".\n\t\t\tconst activeConn = await getOrConnectMcp(capturedServer);\n\t\t\tif (!activeConn) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: `MCP server \"${capturedServer}\" is not connected (reconnect attempt failed)` },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t});\n\n\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\tactiveConn.rpc(\"tools/call\", { name: capturedTool, arguments: params }),\n\t\t\t\t\tabortPromise,\n\t\t\t\t]);\n\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"done\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = result.content\n\t\t\t\t.map((c) => (c.type === \"text\" ? c.text : \"\"))\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (!text) return new Text(\"\", 0, 0);\n\n\t\t\tconst details = result.details as McpToolDetails | undefined;\n\t\t\tconst elapsed = details?.elapsed;\n\t\t\tconst elapsedText = elapsed ? ` ${formatDurationSecs(elapsed / 1000)}` : \"\";\n\t\t\tconst identity = `${capturedServer} › ${capturedTool}`;\n\n\t\t\t// Use context.isError to determine status (framework sets this when tools throw)\n\t\t\tconst isError = context.isError;\n\t\t\tconst statusGlyph = isError ? \"✗\" : \"✓\";\n\t\t\tconst statusLabel = isError ? \"failed\" : \"done\";\n\t\t\tconst statusColor = isError ? \"error\" : \"success\";\n\n\t\t\tconst spine = (s: string) => theme.fg(\"borderMuted\", s);\n\t\t\tconst header =\n\t\t\t\t`${spine(\"╭\")} ${theme.fg(statusColor, statusGlyph)} ` +\n\t\t\t\t`${theme.bold(theme.fg(statusColor, statusLabel))} ` +\n\t\t\t\t`${theme.fg(\"mcp\", identity)}${elapsedText}`;\n\t\t\tconst body = text\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => `${spine(\"│\")} ${theme.fg(\"toolOutput\", line)}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\treturn new Text(`${header}\\n${body}\\n${spine(\"╰\")}`, 0, 0);\n\t\t},\n\t} as ToolDefinition;\n}\n\nconst RESOLVE_MCP_TOOLS_NAME = \"ResolveMcpTools\";\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// A spawned subagent whose tool allowlist has no MCP tools is told by its\n\t\t// parent to skip server connection entirely (see SUBAGENT_SKIP_MCP_ENV).\n\t\t// Each connect is a ~15s-timeout handshake; doing it for a subagent that can\n\t\t// never call the tools is pure startup latency.\n\t\tif (subagentSkipMcp()) return;\n\n\t\tinstallMcpExitCleanup();\n\t\t// Rebuild the status registry from scratch: a reload or session switch runs\n\t\t// this pass again and the startup summary must not double-count.\n\t\tclearMcpServerStatuses();\n\t\tconst allServerConfigs: McpServerConfig[] = [];\n\t\tconst seenNames = new Set<string>();\n\n\t\t// 1. Load from standard mcp.json locations\n\t\t// User-level: ~/.agents/mcp.json\n\t\tconst userAgentsConfig = loadStandardMcpFile(join(homedir(), \".agents\", \"mcp.json\"));\n\t\tfor (const config of userAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Project-level: ./.agents/mcp.json\n\t\tconst projectAgentsConfig = loadStandardMcpFile(join(ctx.cwd, \".agents\", \"mcp.json\"));\n\t\tfor (const config of projectAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Claude Desktop: ~/.config/claude/mcp.json\n\t\tconst claudeDesktopConfig = loadStandardMcpFile(join(homedir(), \".config\", \"claude\", \"mcp.json\"));\n\t\tfor (const config of claudeDesktopConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// 2. Load from hoocode's per-server format (existing behavior)\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || (!serverConfig.command && !serverConfig.url)) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"/\"url\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Skip if already loaded from standard config\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// 2b. Load from plugins/extensions that registered MCP servers during load.\n\t\tfor (const entry of getExtensionMcpServers()) {\n\t\t\tfor (const serverConfig of parseStandardMcpConfig(\n\t\t\t\t{ mcpServers: entry.mcpServers },\n\t\t\t\t`plugin:${entry.source}`,\n\t\t\t)) {\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// Deferral (spec §2): inject MCP tool names only and materialize each schema\n\t\t// on demand via ResolveMcpTools. Default-on and top-level only — a subagent that\n\t\t// needs MCP has this env cleared, so it eager-registers its allowlisted tools\n\t\t// at dispatch (the dispatch ↔ schema interaction) and they are immediately callable.\n\t\tconst defer = deferMcpSchemas();\n\t\tconst deferredCatalog: DeferredMcpToolEntry[] = [];\n\t\t// Retain each deferred tool's raw definition + config so ResolveMcpTools can\n\t\t// build the full ToolDefinition on request.\n\t\tconst deferredByName = new Map<string, { serverConfig: McpServerConfig; tool: McpToolDef }>();\n\t\tconst resolvedNames = new Set<string>();\n\n\t\t// Remote servers get OAuth support: tokens persist under\n\t\t// ~/.hoocode/mcp-auth, and when a server demands interactive (browser)\n\t\t// authorization the handshake fails fast while the flow keeps running in\n\t\t// the background — on completion the server is reconnected and its tools\n\t\t// registered (eagerly, since the deferred catalog is already sealed).\n\t\tconst buildRemoteOptions = (serverConfig: McpServerConfig): McpRemoteOptions => ({\n\t\t\tauthStorageDir: join(HOOCODE_DIR, \"mcp-auth\"),\n\t\t\tonAuthRequired: (authorizationUrl, completed) => {\n\t\t\t\tmcpAuthPending.add(serverConfig.name);\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`MCP: \"${serverConfig.name}\" requires authorization — complete the sign-in opened in your browser` +\n\t\t\t\t\t\t(authorizationUrl ? `: ${authorizationUrl}` : \"\"),\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\tcompleted\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\t\t\t\t\t\t\tfor (const tool of tools) pi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\t\t\tdeferred: false,\n\t\t\t\t\t\t\t\tstate: \"connected\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" after authorization (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\t\t\"info\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: failed to connect \"${serverConfig.name}\" after authorization: ${String(err)}`,\n\t\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\tctx.ui.notify(`MCP: authorization for \"${serverConfig.name}\" failed: ${String(err)}`, \"error\");\n\t\t\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\t// 3. Connect to all servers and register (or defer) tools\n\t\tfor (const serverConfig of allServerConfigs) {\n\t\t\t// Retain the config so a tool call can lazily reconnect a dropped server.\n\t\t\tmcpServerConfigs.set(serverConfig.name, serverConfig);\n\t\t\tmcpRemoteOptions.set(serverConfig.name, buildRemoteOptions(serverConfig));\n\t\t\ttry {\n\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\tif (defer) {\n\t\t\t\t\t\tdeferredCatalog.push({ toolName, server: serverConfig.name, description: tool.description });\n\t\t\t\t\t\tdeferredByName.set(toolName, { serverConfig, tool });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A successful connect is reported by the startup resource summary (one\n\t\t\t\t// `mcp` cell plus a details row), not as a transient line above it.\n\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\tdeferred: defer,\n\t\t\t\t\tstate: \"connected\",\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (mcpAuthPending.has(serverConfig.name)) {\n\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\ttoolCount: 0,\n\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\tdeferred: defer,\n\t\t\t\t\t\tstate: \"authorizing\",\n\t\t\t\t\t});\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: \"${serverConfig.name}\" is waiting for browser authorization; tools will register once it completes`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 4. In deferred mode, register the single resolver that materializes schemas on demand.\n\t\tif (defer && deferredCatalog.length > 0) {\n\t\t\tconst resolveParams = Type.Object(\n\t\t\t\t{\n\t\t\t\t\tnames: Type.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"MCP tool names to make callable (e.g. 'mcp_github_create_pr' or 'create_pr').\",\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{ additionalProperties: false },\n\t\t\t);\n\t\t\tpi.registerTool({\n\t\t\t\tname: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tlabel: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tdescription:\n\t\t\t\t\t\"MCP tools are connected but their schemas are loaded on demand to keep context small. Call this with the tool name(s) you need to make them callable, then call the tool(s). Available MCP tools:\\n\" +\n\t\t\t\t\tformatDeferredCatalog(deferredCatalog),\n\t\t\t\tpromptSnippet: \"Resolve deferred MCP tool schemas by name before calling them.\",\n\t\t\t\tparameters: resolveParams,\n\t\t\t\tasync execute(_toolCallId: string, params: Static<typeof resolveParams>) {\n\t\t\t\t\tconst matched = selectResolvable(deferredCatalog, params.names ?? []);\n\t\t\t\t\tconst newlyResolved: string[] = [];\n\t\t\t\t\tfor (const entry of matched) {\n\t\t\t\t\t\tif (resolvedNames.has(entry.toolName)) continue;\n\t\t\t\t\t\tconst raw = deferredByName.get(entry.toolName);\n\t\t\t\t\t\tif (!raw) continue;\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(raw.serverConfig, raw.tool));\n\t\t\t\t\t\tresolvedNames.add(entry.toolName);\n\t\t\t\t\t\tnewlyResolved.push(entry.toolName);\n\t\t\t\t\t}\n\t\t\t\t\tconst text = matched.length\n\t\t\t\t\t\t? `Resolved ${newlyResolved.length} MCP tool(s): ${matched.map((m) => m.toolName).join(\", \")}. They are now callable.`\n\t\t\t\t\t\t: `No MCP tools matched: ${(params.names ?? []).join(\", \") || \"(none)\"}. See ResolveMcpTools for the catalog.`;\n\t\t\t\t\treturn { content: [{ type: \"text\" as const, text }], details: undefined };\n\t\t\t\t},\n\t\t\t} as ToolDefinition);\n\t\t}\n\t});\n}\n"]}
1
+ {"version":3,"file":"mcp-loader.js","sourceRoot":"","sources":["../../../src/extensions/core/mcp-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP server loader — discovers server configs (standard mcp.json locations,\n * hoocode's per-server JSON files, plugin registrations), connects via JSON-RPC\n * 2.0 over stdio (`command`), Streamable HTTP (`{ \"type\": \"http\", \"url\": ... }`),\n * or legacy SSE (`\"type\": \"sse\"`), and registers each server tool as\n * `mcp_<server>_<tool>`.\n *\n * Config sources (first-wins by server name):\n * 1. ~/.agents/mcp.json (user), ./.agents/mcp.json (project),\n * ~/.config/claude/mcp.json (Claude Desktop)\n * 2. ~/.hoocode/mcp-servers/*.json and ./.hoocode/mcp-servers/*.json\n * 3. MCP servers registered by plugins/extensions during load\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentToolResult, AgentToolUpdateCallback, McpRemoteOptions } from \"@kolisachint/hoocode-agent-core\";\nimport { connectHttpMcpServer, summarizeArgs } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { getHooCodeDir } from \"../../config.js\";\nimport { getExtensionMcpServers } from \"../../core/extension-mcp-servers.js\";\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent, ToolDefinition } from \"../../core/extensions/types.js\";\nimport { formatDurationSecs } from \"../../core/format-duration.js\";\nimport { clearMcpServerStatuses, setMcpServerStatus } from \"../../core/mcp-status.js\";\nimport { deferMcpSchemas, subagentSkipMcp } from \"../../core/subagent-depth.js\";\nimport { taskStore } from \"../../core/task-store.js\";\nimport { type DeferredMcpToolEntry, formatDeferredCatalog, selectResolvable } from \"./mcp-deferred.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\n\ninterface McpToolDetails {\n\telapsed?: number;\n}\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\ninterface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn (stdio transport). One of command/url is required. */\n\tcommand?: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n\t/** Transport: \"stdio\" (default with command), \"http\" (Streamable HTTP), or \"sse\" (legacy) */\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\t/** Remote server URL (http/sse transports) */\n\turl?: string;\n\t/** Extra HTTP headers (e.g. Authorization) for remote transports */\n\theaders?: Record<string, string>;\n\t/** Run MCP tools in background by default (default: true for MCP servers) */\n\tbackground?: boolean;\n}\n\n/** Standard MCP config format used by Claude Desktop, Claude Code, VS Code / Copilot */\ninterface StandardMcpServerConfig {\n\tcommand?: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\ttype?: \"stdio\" | \"http\" | \"sse\";\n\turl?: string;\n\theaders?: Record<string, string>;\n\tbackground?: boolean;\n}\n\ninterface StandardMcpConfig {\n\tmcpServers?: Record<string, StandardMcpServerConfig>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;\n\t/** Send a JSON-RPC notification (no id, no response expected). */\n\tnotify(method: string, params?: unknown): void;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n/**\n * Server configs retained by name so a tool call can transparently reconnect a\n * dropped server (process churn between turns, server exit, a racing teardown)\n * instead of permanently failing with \"not connected\".\n */\nconst mcpServerConfigs = new Map<string, McpServerConfig>();\n/** Remote-transport options (OAuth storage, auth callbacks) per server name. */\nconst mcpRemoteOptions = new Map<string, McpRemoteOptions>();\n/**\n * Servers with a browser authorization flow in flight. While a name is here,\n * a failed handshake must NOT tear the connection down (that would kill the\n * OAuth loopback listener); the auth-completion handler reconnects instead.\n */\nconst mcpAuthPending = new Set<string>();\n\n/** Timeout for the connection handshake (initialize / tools/list). Tool calls\n * themselves are left untimed since MCP tools can be long-running. */\nconst MCP_HANDSHAKE_TIMEOUT_MS = 15000;\n\nfunction spawnMcpServer(config: McpServerConfig & { command: string }): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tif (timeoutMs && timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (pending.delete(id)) {\n\t\t\t\t\t\treject(new Error(`MCP server \"${config.name}\" timed out after ${timeoutMs}ms on ${method}`));\n\t\t\t\t\t}\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimer.unref?.();\n\t\t\t}\n\t\t\tpending.set(id, {\n\t\t\t\tresolve: (r) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tresolve(r);\n\t\t\t\t},\n\t\t\t\treject: (e) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\treject(e);\n\t\t\t\t},\n\t\t\t});\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\tfunction notify(method: string, params?: unknown): void {\n\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n\t}\n\n\treturn {\n\t\trpc,\n\t\tnotify,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\n/** Remote servers use `{ type: \"http\" | \"sse\", url }`; stdio servers use `command`. */\nfunction isRemoteServer(config: McpServerConfig): boolean {\n\treturn config.type === \"http\" || config.type === \"sse\" || (!config.command && typeof config.url === \"string\");\n}\n\nfunction openMcpConnection(config: McpServerConfig): McpConnection {\n\tif (isRemoteServer(config)) {\n\t\tif (typeof config.url !== \"string\") {\n\t\t\tthrow new Error(`MCP server \"${config.name}\" has type \"${config.type}\" but no \"url\"`);\n\t\t}\n\t\treturn connectHttpMcpServer(\n\t\t\t{\n\t\t\t\tname: config.name,\n\t\t\t\turl: config.url,\n\t\t\t\theaders: config.headers,\n\t\t\t\ttype: config.type === \"sse\" ? \"sse\" : \"http\",\n\t\t\t},\n\t\t\tmcpRemoteOptions.get(config.name),\n\t\t);\n\t}\n\tif (typeof config.command !== \"string\") {\n\t\tthrow new Error(`MCP server \"${config.name}\" needs a \"command\" (stdio) or a \"url\" (http/sse)`);\n\t}\n\treturn spawnMcpServer(config as McpServerConfig & { command: string });\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = openMcpConnection(config);\n\tmcpConnections.set(config.name, conn);\n\n\ttry {\n\t\tawait conn.rpc(\n\t\t\t\"initialize\",\n\t\t\t{\n\t\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\t\tcapabilities: { tools: {} },\n\t\t\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t\t\t},\n\t\t\tMCP_HANDSHAKE_TIMEOUT_MS,\n\t\t);\n\n\t\t// Per the MCP spec the client must acknowledge a successful initialize with the\n\t\t// initialized notification before issuing further requests; strict servers gate\n\t\t// tools/call on it.\n\t\tconn.notify(\"notifications/initialized\");\n\n\t\tconst toolsResult = (await conn.rpc(\"tools/list\", {}, MCP_HANDSHAKE_TIMEOUT_MS)) as {\n\t\t\ttools?: McpToolDef[];\n\t\t};\n\t\treturn { conn, tools: toolsResult.tools ?? [] };\n\t} catch (error) {\n\t\t// Don't leave a half-connected entry behind: remote transports have no\n\t\t// process-exit event to evict them, so getOrConnectMcp would keep handing\n\t\t// out the dead connection instead of reconnecting. Exception: a pending\n\t\t// browser authorization — terminating would kill the OAuth loopback\n\t\t// listener; the auth-completion handler reconnects that server itself.\n\t\tif (!mcpAuthPending.has(config.name)) {\n\t\t\tconn.terminate();\n\t\t\tif (mcpConnections.get(config.name) === conn) mcpConnections.delete(config.name);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Return a live connection for a server, lazily reconnecting from the retained\n * config when the previous connection was torn down. Returns undefined only when\n * no config is known or a fresh connect attempt fails.\n */\nasync function getOrConnectMcp(name: string): Promise<McpConnection | undefined> {\n\tconst existing = mcpConnections.get(name);\n\tif (existing) return existing;\n\tconst config = mcpServerConfigs.get(name);\n\tif (!config) return undefined;\n\ttry {\n\t\tconst { conn } = await connectMcpServer(config);\n\t\treturn conn;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nlet mcpExitCleanupInstalled = false;\n/** Kill spawned MCP servers when the host process exits so they don't linger as\n * orphans (their stdin merely goes idle, which doesn't terminate them). */\nfunction installMcpExitCleanup(): void {\n\tif (mcpExitCleanupInstalled) return;\n\tmcpExitCleanupInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const conn of mcpConnections.values()) {\n\t\t\ttry {\n\t\t\t\tconn.terminate();\n\t\t\t} catch {\n\t\t\t\t// best-effort cleanup\n\t\t\t}\n\t\t}\n\t\tmcpConnections.clear();\n\t});\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\n/**\n * Parse standard MCP config format (used by Claude Desktop, VS Code, etc.)\n * into hoocode's McpServerConfig format.\n */\nfunction parseStandardMcpConfig(config: StandardMcpConfig, _source: string): McpServerConfig[] {\n\tif (!config.mcpServers) return [];\n\n\tconst servers: McpServerConfig[] = [];\n\tfor (const [name, serverConfig] of Object.entries(config.mcpServers)) {\n\t\tservers.push({\n\t\t\tname,\n\t\t\tcommand: serverConfig.command,\n\t\t\targs: serverConfig.args,\n\t\t\tenv: serverConfig.env,\n\t\t\ttype: serverConfig.type,\n\t\t\turl: serverConfig.url,\n\t\t\theaders: serverConfig.headers,\n\t\t\tbackground: serverConfig.background,\n\t\t});\n\t}\n\treturn servers;\n}\n\n/**\n * Load MCP servers from a standard mcp.json file.\n * Returns an array of McpServerConfig, or empty array if file doesn't exist or is invalid.\n */\nfunction loadStandardMcpFile(filePath: string): McpServerConfig[] {\n\tif (!existsSync(filePath)) return [];\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf8\");\n\t\tconst config = JSON.parse(content) as StandardMcpConfig;\n\t\treturn parseStandardMcpConfig(config, filePath);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Build the full {@link ToolDefinition} for one MCP tool — the complete JSON\n * schema plus the connect/execute machinery. Shared by the eager path (register\n * every tool up front) and the deferred path (materialize on resolve), so both\n * produce identical, callable tools.\n */\nfunction buildMcpToolDefinition(serverConfig: McpServerConfig, tool: McpToolDef): ToolDefinition {\n\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\tconst schema = buildMcpSchema(tool);\n\tconst capturedServer = serverConfig.name;\n\tconst capturedTool = tool.name;\n\t// MCP tools default to background mode since they are external processes with potential high latency\n\tconst isBackground = serverConfig.background !== false;\n\n\treturn {\n\t\tname: toolName,\n\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\tdescription: tool.description,\n\t\tparameters: schema,\n\t\tbackground: isBackground,\n\t\t// Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —\n\t\t// parallel to the subagent `Task [type] <desc>` line. Without this the\n\t\t// ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.\n\t\t// The args summary reuses the same helper as the background start/finish\n\t\t// messages so the chat title stays in sync with them.\n\t\trenderCall(args, theme) {\n\t\t\tconst summary = summarizeArgs((args ?? {}) as Record<string, unknown>);\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"MCP \")) +\n\t\t\t\ttheme.fg(\"mcp\", `[${capturedServer} › ${capturedTool}]`) +\n\t\t\t\t(summary ? theme.fg(\"dim\", ` ${summary}`) : \"\");\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t\tasync execute(\n\t\t\t_toolCallId: string,\n\t\t\tparams: Static<typeof schema>,\n\t\t\tsignal: AbortSignal,\n\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\tconst startTime = Date.now();\n\t\t\t// Background MCP tools get a task store entry so they appear in the task pane.\n\t\t\t// Foreground tools skip this (their result is awaited inline). The server\n\t\t\t// name rides in subagentMode and becomes the row's `[server]` origin tag;\n\t\t\t// the title carries just the tool.\n\t\t\tconst task = isBackground\n\t\t\t\t? taskStore.create(capturedTool, { source: \"mcp\", subagentMode: capturedServer })\n\t\t\t\t: undefined;\n\t\t\tif (task) taskStore.update(task.id, { status: \"in_progress\" });\n\n\t\t\t// Lazily (re)connect: a dropped connection (server exit, process churn\n\t\t\t// between turns, a racing teardown) should transparently reconnect from\n\t\t\t// the retained config rather than permanently fail with \"not connected\".\n\t\t\tconst activeConn = await getOrConnectMcp(capturedServer);\n\t\t\tif (!activeConn) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: `MCP server \"${capturedServer}\" is not connected (reconnect attempt failed)` },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t});\n\n\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\tactiveConn.rpc(\"tools/call\", { name: capturedTool, arguments: params }),\n\t\t\t\t\tabortPromise,\n\t\t\t\t]);\n\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"done\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\tdetails: { elapsed: Date.now() - startTime } as McpToolDetails,\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = result.content\n\t\t\t\t.map((c) => (c.type === \"text\" ? c.text : \"\"))\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (!text) return new Text(\"\", 0, 0);\n\n\t\t\tconst details = result.details as McpToolDetails | undefined;\n\t\t\tconst elapsed = details?.elapsed;\n\t\t\tconst elapsedText = elapsed ? ` ${formatDurationSecs(elapsed / 1000)}` : \"\";\n\t\t\tconst identity = `${capturedServer} › ${capturedTool}`;\n\n\t\t\t// Use context.isError to determine status (framework sets this when tools throw)\n\t\t\tconst isError = context.isError;\n\t\t\tconst statusGlyph = isError ? \"✗\" : \"✓\";\n\t\t\tconst statusLabel = isError ? \"failed\" : \"done\";\n\t\t\tconst statusColor = isError ? \"error\" : \"success\";\n\n\t\t\tconst spine = (s: string) => theme.fg(\"borderMuted\", s);\n\t\t\tconst header =\n\t\t\t\t`${spine(\"╭\")} ${theme.fg(statusColor, statusGlyph)} ` +\n\t\t\t\t`${theme.bold(theme.fg(statusColor, statusLabel))} ` +\n\t\t\t\t`${theme.fg(\"mcp\", identity)}${elapsedText}`;\n\t\t\tconst body = text\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => `${spine(\"│\")} ${theme.fg(\"toolOutput\", line)}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\treturn new Text(`${header}\\n${body}\\n${spine(\"╰\")}`, 0, 0);\n\t\t},\n\t} as ToolDefinition;\n}\n\nconst RESOLVE_MCP_TOOLS_NAME = \"ResolveMcpTools\";\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// A spawned subagent whose tool allowlist has no MCP tools is told by its\n\t\t// parent to skip server connection entirely (see SUBAGENT_SKIP_MCP_ENV).\n\t\t// Each connect is a ~15s-timeout handshake; doing it for a subagent that can\n\t\t// never call the tools is pure startup latency.\n\t\tif (subagentSkipMcp()) return;\n\n\t\tinstallMcpExitCleanup();\n\t\t// Rebuild the status registry from scratch: a reload or session switch runs\n\t\t// this pass again and the startup summary must not double-count.\n\t\tclearMcpServerStatuses();\n\t\tconst allServerConfigs: McpServerConfig[] = [];\n\t\tconst seenNames = new Set<string>();\n\n\t\t// 1. Load from standard mcp.json locations\n\t\t// User-level: ~/.agents/mcp.json\n\t\tconst userAgentsConfig = loadStandardMcpFile(join(homedir(), \".agents\", \"mcp.json\"));\n\t\tfor (const config of userAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Project-level: ./.agents/mcp.json\n\t\tconst projectAgentsConfig = loadStandardMcpFile(join(ctx.cwd, \".agents\", \"mcp.json\"));\n\t\tfor (const config of projectAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Claude Desktop: ~/.config/claude/mcp.json\n\t\tconst claudeDesktopConfig = loadStandardMcpFile(join(homedir(), \".config\", \"claude\", \"mcp.json\"));\n\t\tfor (const config of claudeDesktopConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// 2. Load from hoocode's per-server format (existing behavior)\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || (!serverConfig.command && !serverConfig.url)) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"/\"url\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Skip if already loaded from standard config\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// 2b. Load from plugins/extensions that registered MCP servers during load.\n\t\tfor (const entry of getExtensionMcpServers()) {\n\t\t\tfor (const serverConfig of parseStandardMcpConfig(\n\t\t\t\t{ mcpServers: entry.mcpServers },\n\t\t\t\t`plugin:${entry.source}`,\n\t\t\t)) {\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// Deferral (spec §2): inject MCP tool names only and materialize each schema\n\t\t// on demand via ResolveMcpTools. Default-on and top-level only — a subagent that\n\t\t// needs MCP has this env cleared, so it eager-registers its allowlisted tools\n\t\t// at dispatch (the dispatch ↔ schema interaction) and they are immediately callable.\n\t\tconst defer = deferMcpSchemas();\n\t\tconst deferredCatalog: DeferredMcpToolEntry[] = [];\n\t\t// Retain each deferred tool's raw definition + config so ResolveMcpTools can\n\t\t// build the full ToolDefinition on request.\n\t\tconst deferredByName = new Map<string, { serverConfig: McpServerConfig; tool: McpToolDef }>();\n\t\tconst resolvedNames = new Set<string>();\n\n\t\t// Remote servers get OAuth support: tokens persist under\n\t\t// ~/.hoocode/mcp-auth, and when a server demands interactive (browser)\n\t\t// authorization the handshake fails fast while the flow keeps running in\n\t\t// the background — on completion the server is reconnected and its tools\n\t\t// registered (eagerly, since the deferred catalog is already sealed).\n\t\tconst buildRemoteOptions = (serverConfig: McpServerConfig): McpRemoteOptions => ({\n\t\t\tauthStorageDir: join(HOOCODE_DIR, \"mcp-auth\"),\n\t\t\tonAuthRequired: (authorizationUrl, completed) => {\n\t\t\t\tmcpAuthPending.add(serverConfig.name);\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`MCP: \"${serverConfig.name}\" requires authorization — complete the sign-in opened in your browser` +\n\t\t\t\t\t\t(authorizationUrl ? `: ${authorizationUrl}` : \"\"),\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\tcompleted\n\t\t\t\t\t.then(async () => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\t\t\t\t\t\t\tfor (const tool of tools) pi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\t\t\tdeferred: false,\n\t\t\t\t\t\t\t\tstate: \"connected\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" after authorization (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"})`,\n\t\t\t\t\t\t\t\t\"info\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\t`MCP: failed to connect \"${serverConfig.name}\" after authorization: ${String(err)}`,\n\t\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: unknown) => {\n\t\t\t\t\t\tmcpAuthPending.delete(serverConfig.name);\n\t\t\t\t\t\tctx.ui.notify(`MCP: authorization for \"${serverConfig.name}\" failed: ${String(err)}`, \"error\");\n\t\t\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\t// 3. Connect to all servers and register (or defer) tools\n\t\tfor (const serverConfig of allServerConfigs) {\n\t\t\t// Retain the config so a tool call can lazily reconnect a dropped server.\n\t\t\tmcpServerConfigs.set(serverConfig.name, serverConfig);\n\t\t\tmcpRemoteOptions.set(serverConfig.name, buildRemoteOptions(serverConfig));\n\t\t\ttry {\n\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\tif (defer) {\n\t\t\t\t\t\tdeferredCatalog.push({ toolName, server: serverConfig.name, description: tool.description });\n\t\t\t\t\t\tdeferredByName.set(toolName, { serverConfig, tool });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A successful connect is reported by the startup resource summary (one\n\t\t\t\t// `mcp` cell plus a details row), not as a transient line above it.\n\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\ttoolCount: tools.length,\n\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\tdeferred: defer,\n\t\t\t\t\tstate: \"connected\",\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (mcpAuthPending.has(serverConfig.name)) {\n\t\t\t\t\tsetMcpServerStatus({\n\t\t\t\t\t\tname: serverConfig.name,\n\t\t\t\t\t\ttoolCount: 0,\n\t\t\t\t\t\tbackground: serverConfig.background !== false,\n\t\t\t\t\t\tdeferred: defer,\n\t\t\t\t\t\tstate: \"authorizing\",\n\t\t\t\t\t});\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP: \"${serverConfig.name}\" is waiting for browser authorization; tools will register once it completes`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 4. In deferred mode, register the single resolver that materializes schemas on demand.\n\t\tif (defer && deferredCatalog.length > 0) {\n\t\t\tconst resolveParams = Type.Object(\n\t\t\t\t{\n\t\t\t\t\tnames: Type.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"MCP tool names to make callable (e.g. 'mcp_github_create_pr' or 'create_pr').\",\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{ additionalProperties: false },\n\t\t\t);\n\t\t\tpi.registerTool({\n\t\t\t\tname: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tlabel: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tdescription:\n\t\t\t\t\t\"MCP tools are connected but their schemas are loaded on demand to keep context small. Call this with the tool name(s) you need to make them callable, then call the tool(s). Available MCP tools:\\n\" +\n\t\t\t\t\tformatDeferredCatalog(deferredCatalog),\n\t\t\t\tpromptSnippet: \"Resolve deferred MCP tool schemas by name before calling them.\",\n\t\t\t\tparameters: resolveParams,\n\t\t\t\tasync execute(_toolCallId: string, params: Static<typeof resolveParams>) {\n\t\t\t\t\tconst matched = selectResolvable(deferredCatalog, params.names ?? []);\n\t\t\t\t\tconst newlyResolved: string[] = [];\n\t\t\t\t\tfor (const entry of matched) {\n\t\t\t\t\t\tif (resolvedNames.has(entry.toolName)) continue;\n\t\t\t\t\t\tconst raw = deferredByName.get(entry.toolName);\n\t\t\t\t\t\tif (!raw) continue;\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(raw.serverConfig, raw.tool));\n\t\t\t\t\t\tresolvedNames.add(entry.toolName);\n\t\t\t\t\t\tnewlyResolved.push(entry.toolName);\n\t\t\t\t\t}\n\t\t\t\t\tconst text = matched.length\n\t\t\t\t\t\t? `Resolved ${newlyResolved.length} MCP tool(s): ${matched.map((m) => m.toolName).join(\", \")}. They are now callable.`\n\t\t\t\t\t\t: `No MCP tools matched: ${(params.names ?? []).join(\", \") || \"(none)\"}. See ResolveMcpTools for the catalog.`;\n\t\t\t\t\treturn { content: [{ type: \"text\" as const, text }], details: undefined };\n\t\t\t\t},\n\t\t\t} as ToolDefinition);\n\t\t}\n\t});\n}\n"]}
@@ -37,14 +37,6 @@ export interface ReuseNudge {
37
37
  /** The reusable-facing note injected into the turn; always points at the plugin layer. */
38
38
  snippet: string;
39
39
  }
40
- /**
41
- * Curated cue → nudge table. Seeded from the static plugin-reuse guidance, but
42
- * keyed on concrete cues that show up in real work (style/policy directives,
43
- * format standardization, explicit capability gaps). Patterns are deliberately
44
- * specific — first-hit arming (see nudges.ts) only stays safe if the cue is
45
- * unambiguous.
46
- */
47
- export declare const REUSE_NUDGES: readonly ReuseNudge[];
48
40
  /** Record that a nudge fired this session so the plugin layer can surface it. */
49
41
  export declare function armReuseNudge(nudge: ReuseNudge): void;
50
42
  /** Reuse nudges that have fired this session, in insertion order. */
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../../src/extensions/core/prompt-reactive/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH,uEAAuE;AACvE,MAAM,WAAW,UAAU;IAC1B,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EA6B7C,CAAC;AAWF,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAErD;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,UAAU,EAAE,CAElD;AAED,0DAA0D;AAC1D,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,UAAU,EAAE,CAG9E;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOpE","sourcesContent":["/**\n * Prompt-reactive reuse policy — the single source of truth for the runtime\n * \"plugin reuse nudge\".\n *\n * The plugin-productivity guidance in `core/tools/plugins.ts` and\n * `core/tools/propose-plugin.ts` ships as static `promptGuidelines` folded into\n * the system prompt once at session build. That text is never re-surfaced when\n * the work actually shows an actionable cue, so the model tends to repeat the\n * same advice by hand instead of reaching for the plugin layer.\n *\n * This module holds the reactive policy that fixes that:\n * - {@link REUSE_NUDGES} the curated cue → nudge table\n * - {@link matchReuseNudges} scan runtime text (tool output, turn text) for cues\n * - the armed registry nudges that fired this process, so the plugin\n * layer can surface them even when no tool asked\n * - {@link isAutonomousPluginSystemEnabled}\n * the one enablement gate shared with the\n * tool-attach path in main.ts (settings.json\n * `enablePluginTools`)\n *\n * The extension in `./nudges.ts` wires this policy to tool/turn events; the\n * policy itself stays pure and independently testable.\n *\n * Conservative by construction: the patterns are specific (a false positive\n * becoming a nag is the real risk), each nudge fires at most once per category\n * per session, and every nudge points back at the plugin layer rather than\n * re-stating advice.\n */\n\nimport { getAgentDir } from \"../../../config.js\";\nimport { SettingsManager } from \"../../../core/settings-manager.js\";\n\n/** A single reactive reuse cue and the plugin-facing nudge it arms. */\nexport interface ReuseNudge {\n\t/** Stable id, also used as the de-dupe key so a nudge fires once per session. */\n\tid: string;\n\t/** Human-facing category label (grouping for the one-per-category cap). */\n\tcategory: string;\n\t/** Specific cue matched against runtime text. Kept tight to avoid false positives. */\n\tpattern: RegExp;\n\t/** The reusable-facing note injected into the turn; always points at the plugin layer. */\n\tsnippet: string;\n}\n\n/**\n * Curated cue → nudge table. Seeded from the static plugin-reuse guidance, but\n * keyed on concrete cues that show up in real work (style/policy directives,\n * format standardization, explicit capability gaps). Patterns are deliberately\n * specific — first-hit arming (see nudges.ts) only stays safe if the cue is\n * unambiguous.\n */\nexport const REUSE_NUDGES: readonly ReuseNudge[] = [\n\t{\n\t\tid: \"style-active-voice\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\b(?:in\\s+)?active voice\\b|\\bavoid(?:ing)?\\s+passive voice\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"active voice\"). If this is a recurring convention, it can live as a reusable skill/command instead of being re-applied by hand — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"style-avoid-repetition\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\bavoid(?:ing)?\\s+repetition\\b|\\bdon['’]?t\\s+repeat\\b|\\bavoid\\s+repeat(?:ing|s)?\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"avoid repetition\"). A reusable skill/command can encode this convention rather than restating it each time — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"format-prefer-json\",\n\t\tcategory: \"output-format\",\n\t\tpattern: /\\bprefer\\s+JSON\\b|\\buse\\s+JSON\\b|\\boutput\\s+(?:as\\s+)?JSON\\b|\\breturn\\s+(?:as\\s+)?JSON\\b/i,\n\t\tsnippet:\n\t\t\t\"An output-format convention is in play (JSON). Standardizing a format is exactly the kind of thing a reusable skill/plugin captures — SearchPlugins for one, or author it with ProposePlugin.\",\n\t},\n\t{\n\t\tid: \"capability-gap\",\n\t\tcategory: \"capability-gap\",\n\t\tpattern: /\\bcapability gap\\b|\\bno tool for\\b|\\blacks? a tool\\b|\\bwish (?:I|we) had a tool\\b/i,\n\t\tsnippet:\n\t\t\t\"This reads like a capability gap. Before hand-rolling it, SearchPlugins for a plugin that fills it (installable and usable this same turn), or author one with ProposePlugin.\",\n\t},\n];\n\n// ── Armed registry (process-scoped) ──────────────────────────────────────────\n//\n// When a nudge fires, its id is recorded here so the plugin layer (SearchPlugins)\n// can surface \"there is already a reuse candidate for this work\" even if the\n// model never explicitly asked. The CLI is single-process per session, so a\n// module-level set is the natural home; nudges.ts clears it on session start.\n\nconst armed = new Map<string, ReuseNudge>();\n\n/** Record that a nudge fired this session so the plugin layer can surface it. */\nexport function armReuseNudge(nudge: ReuseNudge): void {\n\tarmed.set(nudge.id, nudge);\n}\n\n/** Reuse nudges that have fired this session, in insertion order. */\nexport function getArmedReuseNudges(): ReuseNudge[] {\n\treturn [...armed.values()];\n}\n\n/** Reset the armed registry (called on session start). */\nexport function clearArmedReuseNudges(): void {\n\tarmed.clear();\n}\n\n/**\n * Scan a blob of runtime text (tool output, turn text, user prompt) and return\n * every reuse nudge whose cue matches. Pure — arming/de-duping is the caller's\n * job.\n */\nexport function matchReuseNudges(text: string | undefined | null): ReuseNudge[] {\n\tif (!text) return [];\n\treturn REUSE_NUDGES.filter((n) => n.pattern.test(text));\n}\n\n/**\n * The one enablement gate for the whole autonomous plugin system.\n *\n * Reads settings.json `enablePluginTools` (default false) — the same setting\n * that gates whether the plugin lifecycle tools are attached to the top-level\n * agent in main.ts. Keeping both the tool surface and the reactive nudge behind\n * a single flag is the \"whole autonomous plugin system, off by default\" switch.\n */\nexport function isAutonomousPluginSystemEnabled(cwd: string): boolean {\n\ttry {\n\t\treturn SettingsManager.create(cwd, getAgentDir()).getEnablePluginTools();\n\t} catch {\n\t\t// Fail closed: if settings can't be read, treat the autonomous system as off.\n\t\treturn false;\n\t}\n}\n"]}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../../src/extensions/core/prompt-reactive/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH,uEAAuE;AACvE,MAAM,WAAW,UAAU;IAC1B,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;CAChB;AAiDD,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAErD;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,UAAU,EAAE,CAElD;AAED,0DAA0D;AAC1D,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,UAAU,EAAE,CAG9E;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOpE","sourcesContent":["/**\n * Prompt-reactive reuse policy — the single source of truth for the runtime\n * \"plugin reuse nudge\".\n *\n * The plugin-productivity guidance in `core/tools/plugins.ts` and\n * `core/tools/propose-plugin.ts` ships as static `promptGuidelines` folded into\n * the system prompt once at session build. That text is never re-surfaced when\n * the work actually shows an actionable cue, so the model tends to repeat the\n * same advice by hand instead of reaching for the plugin layer.\n *\n * This module holds the reactive policy that fixes that:\n * - {@link REUSE_NUDGES} the curated cue → nudge table\n * - {@link matchReuseNudges} scan runtime text (tool output, turn text) for cues\n * - the armed registry nudges that fired this process, so the plugin\n * layer can surface them even when no tool asked\n * - {@link isAutonomousPluginSystemEnabled}\n * the one enablement gate shared with the\n * tool-attach path in main.ts (settings.json\n * `enablePluginTools`)\n *\n * The extension in `./nudges.ts` wires this policy to tool/turn events; the\n * policy itself stays pure and independently testable.\n *\n * Conservative by construction: the patterns are specific (a false positive\n * becoming a nag is the real risk), each nudge fires at most once per category\n * per session, and every nudge points back at the plugin layer rather than\n * re-stating advice.\n */\n\nimport { getAgentDir } from \"../../../config.js\";\nimport { SettingsManager } from \"../../../core/settings-manager.js\";\n\n/** A single reactive reuse cue and the plugin-facing nudge it arms. */\nexport interface ReuseNudge {\n\t/** Stable id, also used as the de-dupe key so a nudge fires once per session. */\n\tid: string;\n\t/** Human-facing category label (grouping for the one-per-category cap). */\n\tcategory: string;\n\t/** Specific cue matched against runtime text. Kept tight to avoid false positives. */\n\tpattern: RegExp;\n\t/** The reusable-facing note injected into the turn; always points at the plugin layer. */\n\tsnippet: string;\n}\n\n/**\n * Curated cue → nudge table. Seeded from the static plugin-reuse guidance, but\n * keyed on concrete cues that show up in real work (style/policy directives,\n * format standardization, explicit capability gaps). Patterns are deliberately\n * specific — first-hit arming (see nudges.ts) only stays safe if the cue is\n * unambiguous.\n */\nconst REUSE_NUDGES: readonly ReuseNudge[] = [\n\t{\n\t\tid: \"style-active-voice\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\b(?:in\\s+)?active voice\\b|\\bavoid(?:ing)?\\s+passive voice\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"active voice\"). If this is a recurring convention, it can live as a reusable skill/command instead of being re-applied by hand — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"style-avoid-repetition\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\bavoid(?:ing)?\\s+repetition\\b|\\bdon['’]?t\\s+repeat\\b|\\bavoid\\s+repeat(?:ing|s)?\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"avoid repetition\"). A reusable skill/command can encode this convention rather than restating it each time — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"format-prefer-json\",\n\t\tcategory: \"output-format\",\n\t\tpattern: /\\bprefer\\s+JSON\\b|\\buse\\s+JSON\\b|\\boutput\\s+(?:as\\s+)?JSON\\b|\\breturn\\s+(?:as\\s+)?JSON\\b/i,\n\t\tsnippet:\n\t\t\t\"An output-format convention is in play (JSON). Standardizing a format is exactly the kind of thing a reusable skill/plugin captures — SearchPlugins for one, or author it with ProposePlugin.\",\n\t},\n\t{\n\t\tid: \"capability-gap\",\n\t\tcategory: \"capability-gap\",\n\t\tpattern: /\\bcapability gap\\b|\\bno tool for\\b|\\blacks? a tool\\b|\\bwish (?:I|we) had a tool\\b/i,\n\t\tsnippet:\n\t\t\t\"This reads like a capability gap. Before hand-rolling it, SearchPlugins for a plugin that fills it (installable and usable this same turn), or author one with ProposePlugin.\",\n\t},\n];\n\n// ── Armed registry (process-scoped) ──────────────────────────────────────────\n//\n// When a nudge fires, its id is recorded here so the plugin layer (SearchPlugins)\n// can surface \"there is already a reuse candidate for this work\" even if the\n// model never explicitly asked. The CLI is single-process per session, so a\n// module-level set is the natural home; nudges.ts clears it on session start.\n\nconst armed = new Map<string, ReuseNudge>();\n\n/** Record that a nudge fired this session so the plugin layer can surface it. */\nexport function armReuseNudge(nudge: ReuseNudge): void {\n\tarmed.set(nudge.id, nudge);\n}\n\n/** Reuse nudges that have fired this session, in insertion order. */\nexport function getArmedReuseNudges(): ReuseNudge[] {\n\treturn [...armed.values()];\n}\n\n/** Reset the armed registry (called on session start). */\nexport function clearArmedReuseNudges(): void {\n\tarmed.clear();\n}\n\n/**\n * Scan a blob of runtime text (tool output, turn text, user prompt) and return\n * every reuse nudge whose cue matches. Pure — arming/de-duping is the caller's\n * job.\n */\nexport function matchReuseNudges(text: string | undefined | null): ReuseNudge[] {\n\tif (!text) return [];\n\treturn REUSE_NUDGES.filter((n) => n.pattern.test(text));\n}\n\n/**\n * The one enablement gate for the whole autonomous plugin system.\n *\n * Reads settings.json `enablePluginTools` (default false) — the same setting\n * that gates whether the plugin lifecycle tools are attached to the top-level\n * agent in main.ts. Keeping both the tool surface and the reactive nudge behind\n * a single flag is the \"whole autonomous plugin system, off by default\" switch.\n */\nexport function isAutonomousPluginSystemEnabled(cwd: string): boolean {\n\ttry {\n\t\treturn SettingsManager.create(cwd, getAgentDir()).getEnablePluginTools();\n\t} catch {\n\t\t// Fail closed: if settings can't be read, treat the autonomous system as off.\n\t\treturn false;\n\t}\n}\n"]}
@@ -35,7 +35,7 @@ import { SettingsManager } from "../../../core/settings-manager.js";
35
35
  * specific — first-hit arming (see nudges.ts) only stays safe if the cue is
36
36
  * unambiguous.
37
37
  */
38
- export const REUSE_NUDGES = [
38
+ const REUSE_NUDGES = [
39
39
  {
40
40
  id: "style-active-voice",
41
41
  category: "writing-style",
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../src/extensions/core/prompt-reactive/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAcpE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAA0B;IAClD;QACC,EAAE,EAAE,oBAAoB;QACxB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,+DAA+D;QACxE,OAAO,EACN,8NAA4N;KAC7N;IACD;QACC,EAAE,EAAE,wBAAwB;QAC5B,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,uFAAqF;QAC9F,OAAO,EACN,0MAAwM;KACzM;IACD;QACC,EAAE,EAAE,oBAAoB;QACxB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,2FAA2F;QACpG,OAAO,EACN,iMAA+L;KAChM;IACD;QACC,EAAE,EAAE,gBAAgB;QACpB,QAAQ,EAAE,gBAAgB;QAC1B,OAAO,EAAE,oFAAoF;QAC7F,OAAO,EACN,+KAA+K;KAChL;CACD,CAAC;AAEF,wKAAgF;AAChF,EAAE;AACF,kFAAkF;AAClF,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAE9E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAE5C,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,KAAiB,EAAQ;IACtD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAAA,CAC3B;AAED,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,GAAiB;IACnD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;AAAA,CAC3B;AAED,0DAA0D;AAC1D,MAAM,UAAU,qBAAqB,GAAS;IAC7C,KAAK,CAAC,KAAK,EAAE,CAAC;AAAA,CACd;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA+B,EAAgB;IAC/E,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAAA,CACxD;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,GAAW,EAAW;IACrE,IAAI,CAAC;QACJ,OAAO,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACR,8EAA8E;QAC9E,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD","sourcesContent":["/**\n * Prompt-reactive reuse policy — the single source of truth for the runtime\n * \"plugin reuse nudge\".\n *\n * The plugin-productivity guidance in `core/tools/plugins.ts` and\n * `core/tools/propose-plugin.ts` ships as static `promptGuidelines` folded into\n * the system prompt once at session build. That text is never re-surfaced when\n * the work actually shows an actionable cue, so the model tends to repeat the\n * same advice by hand instead of reaching for the plugin layer.\n *\n * This module holds the reactive policy that fixes that:\n * - {@link REUSE_NUDGES} the curated cue → nudge table\n * - {@link matchReuseNudges} scan runtime text (tool output, turn text) for cues\n * - the armed registry nudges that fired this process, so the plugin\n * layer can surface them even when no tool asked\n * - {@link isAutonomousPluginSystemEnabled}\n * the one enablement gate shared with the\n * tool-attach path in main.ts (settings.json\n * `enablePluginTools`)\n *\n * The extension in `./nudges.ts` wires this policy to tool/turn events; the\n * policy itself stays pure and independently testable.\n *\n * Conservative by construction: the patterns are specific (a false positive\n * becoming a nag is the real risk), each nudge fires at most once per category\n * per session, and every nudge points back at the plugin layer rather than\n * re-stating advice.\n */\n\nimport { getAgentDir } from \"../../../config.js\";\nimport { SettingsManager } from \"../../../core/settings-manager.js\";\n\n/** A single reactive reuse cue and the plugin-facing nudge it arms. */\nexport interface ReuseNudge {\n\t/** Stable id, also used as the de-dupe key so a nudge fires once per session. */\n\tid: string;\n\t/** Human-facing category label (grouping for the one-per-category cap). */\n\tcategory: string;\n\t/** Specific cue matched against runtime text. Kept tight to avoid false positives. */\n\tpattern: RegExp;\n\t/** The reusable-facing note injected into the turn; always points at the plugin layer. */\n\tsnippet: string;\n}\n\n/**\n * Curated cue → nudge table. Seeded from the static plugin-reuse guidance, but\n * keyed on concrete cues that show up in real work (style/policy directives,\n * format standardization, explicit capability gaps). Patterns are deliberately\n * specific — first-hit arming (see nudges.ts) only stays safe if the cue is\n * unambiguous.\n */\nexport const REUSE_NUDGES: readonly ReuseNudge[] = [\n\t{\n\t\tid: \"style-active-voice\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\b(?:in\\s+)?active voice\\b|\\bavoid(?:ing)?\\s+passive voice\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"active voice\"). If this is a recurring convention, it can live as a reusable skill/command instead of being re-applied by hand — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"style-avoid-repetition\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\bavoid(?:ing)?\\s+repetition\\b|\\bdon['’]?t\\s+repeat\\b|\\bavoid\\s+repeat(?:ing|s)?\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"avoid repetition\"). A reusable skill/command can encode this convention rather than restating it each time — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"format-prefer-json\",\n\t\tcategory: \"output-format\",\n\t\tpattern: /\\bprefer\\s+JSON\\b|\\buse\\s+JSON\\b|\\boutput\\s+(?:as\\s+)?JSON\\b|\\breturn\\s+(?:as\\s+)?JSON\\b/i,\n\t\tsnippet:\n\t\t\t\"An output-format convention is in play (JSON). Standardizing a format is exactly the kind of thing a reusable skill/plugin captures — SearchPlugins for one, or author it with ProposePlugin.\",\n\t},\n\t{\n\t\tid: \"capability-gap\",\n\t\tcategory: \"capability-gap\",\n\t\tpattern: /\\bcapability gap\\b|\\bno tool for\\b|\\blacks? a tool\\b|\\bwish (?:I|we) had a tool\\b/i,\n\t\tsnippet:\n\t\t\t\"This reads like a capability gap. Before hand-rolling it, SearchPlugins for a plugin that fills it (installable and usable this same turn), or author one with ProposePlugin.\",\n\t},\n];\n\n// ── Armed registry (process-scoped) ──────────────────────────────────────────\n//\n// When a nudge fires, its id is recorded here so the plugin layer (SearchPlugins)\n// can surface \"there is already a reuse candidate for this work\" even if the\n// model never explicitly asked. The CLI is single-process per session, so a\n// module-level set is the natural home; nudges.ts clears it on session start.\n\nconst armed = new Map<string, ReuseNudge>();\n\n/** Record that a nudge fired this session so the plugin layer can surface it. */\nexport function armReuseNudge(nudge: ReuseNudge): void {\n\tarmed.set(nudge.id, nudge);\n}\n\n/** Reuse nudges that have fired this session, in insertion order. */\nexport function getArmedReuseNudges(): ReuseNudge[] {\n\treturn [...armed.values()];\n}\n\n/** Reset the armed registry (called on session start). */\nexport function clearArmedReuseNudges(): void {\n\tarmed.clear();\n}\n\n/**\n * Scan a blob of runtime text (tool output, turn text, user prompt) and return\n * every reuse nudge whose cue matches. Pure — arming/de-duping is the caller's\n * job.\n */\nexport function matchReuseNudges(text: string | undefined | null): ReuseNudge[] {\n\tif (!text) return [];\n\treturn REUSE_NUDGES.filter((n) => n.pattern.test(text));\n}\n\n/**\n * The one enablement gate for the whole autonomous plugin system.\n *\n * Reads settings.json `enablePluginTools` (default false) — the same setting\n * that gates whether the plugin lifecycle tools are attached to the top-level\n * agent in main.ts. Keeping both the tool surface and the reactive nudge behind\n * a single flag is the \"whole autonomous plugin system, off by default\" switch.\n */\nexport function isAutonomousPluginSystemEnabled(cwd: string): boolean {\n\ttry {\n\t\treturn SettingsManager.create(cwd, getAgentDir()).getEnablePluginTools();\n\t} catch {\n\t\t// Fail closed: if settings can't be read, treat the autonomous system as off.\n\t\treturn false;\n\t}\n}\n"]}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../src/extensions/core/prompt-reactive/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAcpE;;;;;;GAMG;AACH,MAAM,YAAY,GAA0B;IAC3C;QACC,EAAE,EAAE,oBAAoB;QACxB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,+DAA+D;QACxE,OAAO,EACN,8NAA4N;KAC7N;IACD;QACC,EAAE,EAAE,wBAAwB;QAC5B,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,uFAAqF;QAC9F,OAAO,EACN,0MAAwM;KACzM;IACD;QACC,EAAE,EAAE,oBAAoB;QACxB,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,2FAA2F;QACpG,OAAO,EACN,iMAA+L;KAChM;IACD;QACC,EAAE,EAAE,gBAAgB;QACpB,QAAQ,EAAE,gBAAgB;QAC1B,OAAO,EAAE,oFAAoF;QAC7F,OAAO,EACN,+KAA+K;KAChL;CACD,CAAC;AAEF,wKAAgF;AAChF,EAAE;AACF,kFAAkF;AAClF,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAE9E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAE5C,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,KAAiB,EAAQ;IACtD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAAA,CAC3B;AAED,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,GAAiB;IACnD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;AAAA,CAC3B;AAED,0DAA0D;AAC1D,MAAM,UAAU,qBAAqB,GAAS;IAC7C,KAAK,CAAC,KAAK,EAAE,CAAC;AAAA,CACd;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA+B,EAAgB;IAC/E,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAAA,CACxD;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,GAAW,EAAW;IACrE,IAAI,CAAC;QACJ,OAAO,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACR,8EAA8E;QAC9E,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD","sourcesContent":["/**\n * Prompt-reactive reuse policy — the single source of truth for the runtime\n * \"plugin reuse nudge\".\n *\n * The plugin-productivity guidance in `core/tools/plugins.ts` and\n * `core/tools/propose-plugin.ts` ships as static `promptGuidelines` folded into\n * the system prompt once at session build. That text is never re-surfaced when\n * the work actually shows an actionable cue, so the model tends to repeat the\n * same advice by hand instead of reaching for the plugin layer.\n *\n * This module holds the reactive policy that fixes that:\n * - {@link REUSE_NUDGES} the curated cue → nudge table\n * - {@link matchReuseNudges} scan runtime text (tool output, turn text) for cues\n * - the armed registry nudges that fired this process, so the plugin\n * layer can surface them even when no tool asked\n * - {@link isAutonomousPluginSystemEnabled}\n * the one enablement gate shared with the\n * tool-attach path in main.ts (settings.json\n * `enablePluginTools`)\n *\n * The extension in `./nudges.ts` wires this policy to tool/turn events; the\n * policy itself stays pure and independently testable.\n *\n * Conservative by construction: the patterns are specific (a false positive\n * becoming a nag is the real risk), each nudge fires at most once per category\n * per session, and every nudge points back at the plugin layer rather than\n * re-stating advice.\n */\n\nimport { getAgentDir } from \"../../../config.js\";\nimport { SettingsManager } from \"../../../core/settings-manager.js\";\n\n/** A single reactive reuse cue and the plugin-facing nudge it arms. */\nexport interface ReuseNudge {\n\t/** Stable id, also used as the de-dupe key so a nudge fires once per session. */\n\tid: string;\n\t/** Human-facing category label (grouping for the one-per-category cap). */\n\tcategory: string;\n\t/** Specific cue matched against runtime text. Kept tight to avoid false positives. */\n\tpattern: RegExp;\n\t/** The reusable-facing note injected into the turn; always points at the plugin layer. */\n\tsnippet: string;\n}\n\n/**\n * Curated cue → nudge table. Seeded from the static plugin-reuse guidance, but\n * keyed on concrete cues that show up in real work (style/policy directives,\n * format standardization, explicit capability gaps). Patterns are deliberately\n * specific — first-hit arming (see nudges.ts) only stays safe if the cue is\n * unambiguous.\n */\nconst REUSE_NUDGES: readonly ReuseNudge[] = [\n\t{\n\t\tid: \"style-active-voice\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\b(?:in\\s+)?active voice\\b|\\bavoid(?:ing)?\\s+passive voice\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"active voice\"). If this is a recurring convention, it can live as a reusable skill/command instead of being re-applied by hand — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"style-avoid-repetition\",\n\t\tcategory: \"writing-style\",\n\t\tpattern: /\\bavoid(?:ing)?\\s+repetition\\b|\\bdon['’]?t\\s+repeat\\b|\\bavoid\\s+repeat(?:ing|s)?\\b/i,\n\t\tsnippet:\n\t\t\t'A writing-style rule is in play (\"avoid repetition\"). A reusable skill/command can encode this convention rather than restating it each time — SearchPlugins for one, or author it with ProposePlugin.',\n\t},\n\t{\n\t\tid: \"format-prefer-json\",\n\t\tcategory: \"output-format\",\n\t\tpattern: /\\bprefer\\s+JSON\\b|\\buse\\s+JSON\\b|\\boutput\\s+(?:as\\s+)?JSON\\b|\\breturn\\s+(?:as\\s+)?JSON\\b/i,\n\t\tsnippet:\n\t\t\t\"An output-format convention is in play (JSON). Standardizing a format is exactly the kind of thing a reusable skill/plugin captures — SearchPlugins for one, or author it with ProposePlugin.\",\n\t},\n\t{\n\t\tid: \"capability-gap\",\n\t\tcategory: \"capability-gap\",\n\t\tpattern: /\\bcapability gap\\b|\\bno tool for\\b|\\blacks? a tool\\b|\\bwish (?:I|we) had a tool\\b/i,\n\t\tsnippet:\n\t\t\t\"This reads like a capability gap. Before hand-rolling it, SearchPlugins for a plugin that fills it (installable and usable this same turn), or author one with ProposePlugin.\",\n\t},\n];\n\n// ── Armed registry (process-scoped) ──────────────────────────────────────────\n//\n// When a nudge fires, its id is recorded here so the plugin layer (SearchPlugins)\n// can surface \"there is already a reuse candidate for this work\" even if the\n// model never explicitly asked. The CLI is single-process per session, so a\n// module-level set is the natural home; nudges.ts clears it on session start.\n\nconst armed = new Map<string, ReuseNudge>();\n\n/** Record that a nudge fired this session so the plugin layer can surface it. */\nexport function armReuseNudge(nudge: ReuseNudge): void {\n\tarmed.set(nudge.id, nudge);\n}\n\n/** Reuse nudges that have fired this session, in insertion order. */\nexport function getArmedReuseNudges(): ReuseNudge[] {\n\treturn [...armed.values()];\n}\n\n/** Reset the armed registry (called on session start). */\nexport function clearArmedReuseNudges(): void {\n\tarmed.clear();\n}\n\n/**\n * Scan a blob of runtime text (tool output, turn text, user prompt) and return\n * every reuse nudge whose cue matches. Pure — arming/de-duping is the caller's\n * job.\n */\nexport function matchReuseNudges(text: string | undefined | null): ReuseNudge[] {\n\tif (!text) return [];\n\treturn REUSE_NUDGES.filter((n) => n.pattern.test(text));\n}\n\n/**\n * The one enablement gate for the whole autonomous plugin system.\n *\n * Reads settings.json `enablePluginTools` (default false) — the same setting\n * that gates whether the plugin lifecycle tools are attached to the top-level\n * agent in main.ts. Keeping both the tool surface and the reactive nudge behind\n * a single flag is the \"whole autonomous plugin system, off by default\" switch.\n */\nexport function isAutonomousPluginSystemEnabled(cwd: string): boolean {\n\ttry {\n\t\treturn SettingsManager.create(cwd, getAgentDir()).getEnablePluginTools();\n\t} catch {\n\t\t// Fail closed: if settings can't be read, treat the autonomous system as off.\n\t\treturn false;\n\t}\n}\n"]}