@herbertgao/pi-extensions 2026.8.3 → 2026.8.5

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 (408) hide show
  1. package/README.md +45 -4
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  31. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  32. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  33. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  34. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  35. package/node_modules/pi-footer/LICENSE +21 -0
  36. package/node_modules/pi-footer/README.md +354 -0
  37. package/node_modules/pi-footer/package.json +68 -0
  38. package/node_modules/pi-footer/src/cache.ts +106 -0
  39. package/node_modules/pi-footer/src/colors.ts +237 -0
  40. package/node_modules/pi-footer/src/config.ts +202 -0
  41. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  42. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  43. package/node_modules/pi-footer/src/git.ts +135 -0
  44. package/node_modules/pi-footer/src/index.ts +268 -0
  45. package/node_modules/pi-footer/src/metrics.ts +141 -0
  46. package/node_modules/pi-footer/src/presets.ts +383 -0
  47. package/node_modules/pi-footer/src/render.ts +114 -0
  48. package/node_modules/pi-footer/src/separators.ts +52 -0
  49. package/node_modules/pi-footer/src/types.ts +122 -0
  50. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  51. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  52. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  53. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  54. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  55. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  56. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  57. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  58. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  59. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  60. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  61. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  62. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  63. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  64. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  65. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  66. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  67. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  68. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  69. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  70. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  71. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  72. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  73. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  74. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  75. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  76. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  77. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  78. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  79. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  80. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  81. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  82. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  83. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  84. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  85. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  86. package/node_modules/pi-footer/src/ui.ts +66 -0
  87. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  88. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  89. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  90. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  91. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  92. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  93. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  94. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  95. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  96. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  97. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  99. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  100. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  102. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  103. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  105. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  108. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  109. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  111. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  114. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  115. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  116. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  117. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  118. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  119. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  120. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  121. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  122. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  123. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  124. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  125. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  126. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  127. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  128. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  130. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  132. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  133. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  134. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  135. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  136. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  137. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  138. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  148. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  149. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  150. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  151. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  152. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  153. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  154. package/node_modules/pi-lens/README.md +13 -0
  155. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  156. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  157. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  158. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  160. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  161. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  162. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  163. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  164. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  165. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  166. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  167. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  168. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  169. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  170. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  171. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  172. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  173. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  174. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  175. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  177. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  178. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  179. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  180. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  181. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  182. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  183. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  184. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  185. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  186. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  187. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  188. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  189. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  190. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  191. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  192. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  193. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  194. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  195. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  196. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  197. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  198. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  199. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  201. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  202. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  204. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  205. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  206. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  219. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  220. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  221. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  222. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  223. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  224. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  225. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  226. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  227. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  228. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  229. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  230. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  231. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  232. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  233. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  234. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  235. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  236. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  237. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  238. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  239. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  240. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  241. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  242. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  243. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  244. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  245. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  246. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  247. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  248. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  249. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  250. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  251. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  252. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  253. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  254. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  255. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  256. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  257. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  258. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  259. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  260. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  261. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  262. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  263. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  264. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  265. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  266. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  267. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  268. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  269. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  270. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  271. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  272. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  273. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  274. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  275. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  277. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  278. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  279. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  280. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  281. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  282. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  283. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  284. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  285. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  286. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  287. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  288. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  289. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  290. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  291. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  292. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  293. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  294. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  295. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  296. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  297. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  298. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  299. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  300. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  301. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  302. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  303. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  304. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  305. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  306. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  307. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  308. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  309. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  310. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  311. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  313. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  314. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  315. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  316. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  317. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  318. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  319. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  320. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  321. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  322. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  323. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  324. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  325. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  326. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  327. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  328. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  329. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  330. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  331. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  332. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  333. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  334. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  335. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  336. package/node_modules/pi-lens/dist/index.js +49361 -36888
  337. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  338. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  339. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  340. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  341. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  342. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  343. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  344. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  345. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  346. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  347. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  348. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  349. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  350. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  351. package/node_modules/pi-lens/docs/fable.md +27 -0
  352. package/node_modules/pi-lens/docs/features.md +31 -2
  353. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  354. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  355. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  356. package/node_modules/pi-lens/docs/mcp.md +37 -4
  357. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  358. package/node_modules/pi-lens/docs/settings.md +12 -1
  359. package/node_modules/pi-lens/docs/tools.md +4 -1
  360. package/node_modules/pi-lens/docs/usage.md +18 -0
  361. package/node_modules/pi-lens/package.json +10 -10
  362. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  363. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  364. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  365. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  366. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  367. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  368. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  369. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  370. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  371. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  372. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  373. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  374. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  375. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  376. package/node_modules/pi-mcp-adapter/README.md +34 -3
  377. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  378. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  379. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  380. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  381. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  382. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  383. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  384. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  385. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  386. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  387. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  388. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  389. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  390. package/node_modules/pi-mcp-adapter/package.json +3 -3
  391. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  392. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  393. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  394. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  395. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  396. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  397. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  398. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  399. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  400. package/package.json +17 -13
  401. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  402. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  403. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  404. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  405. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  406. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  407. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  408. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -16,7 +16,7 @@ import { maybeStartUiSession, summarizeUiSessionResult, type UiSessionRuntime }
16
16
  import { formatAuthRequiredMessage, formatMcpStatus, resolveServerUrl, truncateAtWord } from "./utils.ts";
17
17
  import { authenticate, completeAuthFromInput, startAuth, supportsOAuth } from "./mcp-auth-flow.ts";
18
18
  import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.ts";
19
- import { paginate, rankSuggestions, rankToolMatches } from "./search-ranking.ts";
19
+ import { paginate, rankSuggestions, rankToolMatches, resolveSearchKeywords } from "./search-ranking.ts";
20
20
  import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.ts";
21
21
 
22
22
  type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
@@ -37,6 +37,36 @@ type AutoAuthResult =
37
37
  | { status: "success" }
38
38
  | { status: "failed"; message: string };
39
39
 
40
+ function getToolMatches(metadata: ToolMetadata[] | undefined, toolName: string, exact: boolean): ToolMetadata[] {
41
+ if (!metadata) return [];
42
+ if (exact) return metadata.filter(tool => tool.name === toolName);
43
+ const normalizedName = toolName.replace(/-/g, "_");
44
+ return metadata.filter(tool => tool.name.replace(/-/g, "_") === normalizedName);
45
+ }
46
+
47
+ function getEnabledToolMatches(state: McpExtensionState, toolName: string, exact: boolean): { server: string; tool: ToolMetadata }[] {
48
+ const matches: { server: string; tool: ToolMetadata }[] = [];
49
+ for (const [server, metadata] of state.toolMetadata) {
50
+ if (isServerDisabled(state.config.mcpServers[server])) continue;
51
+ for (const tool of getToolMatches(metadata, toolName, exact)) matches.push({ server, tool });
52
+ }
53
+ return matches;
54
+ }
55
+
56
+ function getSingleToolMatch(metadata: ToolMetadata[] | undefined, toolName: string): ToolMetadata | "ambiguous" | undefined {
57
+ const exactMatches = getToolMatches(metadata, toolName, true);
58
+ const matches = exactMatches.length > 0 ? exactMatches : getToolMatches(metadata, toolName, false);
59
+ return matches.length > 1 ? "ambiguous" : matches[0];
60
+ }
61
+
62
+ function ambiguousToolResult(mode: "call" | "describe", toolName: string): ProxyToolResult {
63
+ const message = `Tool "${toolName}" matches multiple servers. Specify a server.`;
64
+ return {
65
+ content: [{ type: "text" as const, text: message }],
66
+ details: { mode, error: "ambiguous_tool", requestedTool: toolName, message },
67
+ };
68
+ }
69
+
40
70
  function disabledResult(mode: string, serverName: string): ProxyToolResult {
41
71
  const message = `Server "${serverName}" is disabled. Run /mcp enable ${serverName} and /reload to enable it.`;
42
72
  return {
@@ -404,20 +434,28 @@ export async function executeAuthComplete(state: McpExtensionState, serverName:
404
434
  }
405
435
 
406
436
  export function executeDescribe(state: McpExtensionState, toolName: string): ProxyToolResult {
407
- let serverName: string | undefined;
408
- let toolMeta: ToolMetadata | undefined;
437
+ const exactMatches = getEnabledToolMatches(state, toolName, true);
438
+ if (exactMatches.length > 1) return ambiguousToolResult("describe", toolName);
439
+ if (exactMatches.length === 0 && getEnabledToolMatches(state, toolName, false).length > 1) {
440
+ return ambiguousToolResult("describe", toolName);
441
+ }
442
+
443
+ let serverName = exactMatches[0]?.server;
444
+ let toolMeta = exactMatches[0]?.tool;
409
445
  let disabledMatch: string | undefined;
410
446
 
411
- for (const [server, metadata] of state.toolMetadata.entries()) {
412
- const found = findToolByName(metadata, toolName);
413
- if (!found) continue;
414
- if (isServerDisabled(state.config.mcpServers[server])) {
415
- disabledMatch ??= server;
416
- continue;
447
+ if (!toolMeta) {
448
+ for (const [server, metadata] of state.toolMetadata.entries()) {
449
+ const found = findToolByName(metadata, toolName);
450
+ if (!found) continue;
451
+ if (isServerDisabled(state.config.mcpServers[server])) {
452
+ disabledMatch ??= server;
453
+ continue;
454
+ }
455
+ serverName = server;
456
+ toolMeta = found;
457
+ break;
417
458
  }
418
- serverName = server;
419
- toolMeta = found;
420
- break;
421
459
  }
422
460
 
423
461
  if (!serverName || !toolMeta) {
@@ -430,7 +468,7 @@ export function executeDescribe(state: McpExtensionState, toolName: string): Pro
430
468
  };
431
469
  }
432
470
 
433
- const approvalMarker = isToolCallApprovalRequired(state.config, serverName, toolMeta)
471
+ const approvalMarker = isToolCallApprovalRequired(state.config, serverName, toolMeta, state.toolMetadata)
434
472
  ? " (requires approval)"
435
473
  : "";
436
474
  let text = `${toolMeta.name}${approvalMarker}\n`;
@@ -503,11 +541,15 @@ export function executeSearch(
503
541
  }
504
542
 
505
543
  matches = [];
544
+ const globalPrefix = state.config.settings?.toolPrefix ?? "server";
506
545
  for (const [serverName, metadata] of state.toolMetadata.entries()) {
507
- if (isServerDisabled(state.config.mcpServers[serverName])) continue;
546
+ const definition = state.config.mcpServers[serverName];
547
+ if (isServerDisabled(definition)) continue;
508
548
  if (server && serverName !== server) continue;
509
549
  for (const tool of metadata) {
510
- if (pattern.test(tool.name) || pattern.test(tool.description)) matches.push({ server: serverName, tool, score: 0 });
550
+ const matched = pattern.test(tool.name) || pattern.test(tool.description)
551
+ || resolveSearchKeywords(definition, tool.originalName, serverName, globalPrefix).some(keyword => pattern.test(keyword));
552
+ if (matched) matches.push({ server: serverName, tool, score: 0 });
511
553
  }
512
554
  }
513
555
  } else if (query.trim().length === 0) {
@@ -553,7 +595,7 @@ export function executeSearch(
553
595
 
554
596
  let text = `Found ${page.total} tool${page.total === 1 ? "" : "s"} matching "${query}":\n\n`;
555
597
  for (const match of page.items) {
556
- const approvalMarker = isToolCallApprovalRequired(state.config, match.server, match.tool)
598
+ const approvalMarker = isToolCallApprovalRequired(state.config, match.server, match.tool, state.toolMetadata)
557
599
  ? " (requires approval)"
558
600
  : "";
559
601
  if (showSchemas) {
@@ -733,7 +775,7 @@ export async function executeConnect(state: McpExtensionState, serverName: strin
733
775
  }
734
776
  }
735
777
  const prefix = state.config.settings?.toolPrefix ?? "server";
736
- const { metadata } = buildToolMetadata(connection.tools, connection.resources, definition, serverName, prefix);
778
+ const { metadata } = buildToolMetadata(connection.tools, connection.resources, definition, serverName, prefix, state.config.mcpServers, state.toolMetadata);
737
779
  state.toolMetadata.set(serverName, metadata);
738
780
  if (!connection.promptDiscoveryFailed) {
739
781
  state.promptMetadata?.set(serverName, reconstructPromptMetadata(serverName, connection.prompts ?? [], prefix, definition));
@@ -801,14 +843,22 @@ export async function executeCall(
801
843
  };
802
844
  }
803
845
  if (serverName) {
804
- toolMeta = findToolByName(state.toolMetadata.get(serverName), toolName);
846
+ const match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);
847
+ if (match === "ambiguous") return ambiguousToolResult("call", toolName);
848
+ toolMeta = match;
805
849
  if (isServerDisabled(state.config.mcpServers[serverName])) {
806
850
  return disabledCallResult(serverName, toolMeta);
807
851
  }
808
852
  } else {
853
+ const exactMatches = getEnabledToolMatches(state, toolName, true);
854
+ if (exactMatches.length > 1) return ambiguousToolResult("call", toolName);
855
+ if (exactMatches.length === 0 && getEnabledToolMatches(state, toolName, false).length > 1) {
856
+ return ambiguousToolResult("call", toolName);
857
+ }
858
+
809
859
  let disabledMatch: { serverName: string; toolMeta: ToolMetadata } | undefined;
810
860
  for (const [server, metadata] of state.toolMetadata.entries()) {
811
- const found = findToolByName(metadata, toolName);
861
+ const found = metadata.find(tool => tool.name === toolName);
812
862
  if (!found) continue;
813
863
  if (isServerDisabled(state.config.mcpServers[server])) {
814
864
  disabledMatch ??= { serverName: server, toolMeta: found };
@@ -818,13 +868,28 @@ export async function executeCall(
818
868
  toolMeta = found;
819
869
  break;
820
870
  }
871
+ if (!toolMeta && !disabledMatch) {
872
+ for (const [server, metadata] of state.toolMetadata.entries()) {
873
+ const found = findToolByName(metadata, toolName);
874
+ if (!found) continue;
875
+ if (isServerDisabled(state.config.mcpServers[server])) {
876
+ disabledMatch ??= { serverName: server, toolMeta: found };
877
+ continue;
878
+ }
879
+ serverName = server;
880
+ toolMeta = found;
881
+ break;
882
+ }
883
+ }
821
884
  if (!toolMeta && disabledMatch) return disabledCallResult(disabledMatch.serverName, disabledMatch.toolMeta);
822
885
  }
823
886
 
824
887
  if (serverName && !toolMeta) {
825
888
  const connected = await lazyConnect(state, serverName, ownedSignal);
826
889
  if (connected) {
827
- toolMeta = findToolByName(state.toolMetadata.get(serverName), toolName);
890
+ const match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);
891
+ if (match === "ambiguous") return ambiguousToolResult("call", toolName);
892
+ toolMeta = match;
828
893
  } else {
829
894
  const needsAuthConnection = state.manager.getConnection(serverName);
830
895
  if (needsAuthConnection?.status === "needs-auth") {
@@ -842,7 +907,9 @@ export async function executeCall(
842
907
  clearFailure(state, serverName);
843
908
  const connectedAfterAuth = await lazyConnect(state, serverName, ownedSignal);
844
909
  if (connectedAfterAuth) {
845
- toolMeta = findToolByName(state.toolMetadata.get(serverName), toolName);
910
+ const match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);
911
+ if (match === "ambiguous") return ambiguousToolResult("call", toolName);
912
+ toolMeta = match;
846
913
  if (!toolMeta) {
847
914
  const suggestions = rankSuggestions(state, toolName, 5);
848
915
  const suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}` : "";
@@ -879,6 +946,8 @@ export async function executeCall(
879
946
  let prefixMatchedServer: string | undefined;
880
947
 
881
948
  if (!serverName && !toolMeta && prefixMode !== "none") {
949
+ const lazyExactMatches: { serverName: string; toolMeta: ToolMetadata }[] = [];
950
+ const lazyFallbackMatches: { serverName: string; toolMeta: ToolMetadata }[] = [];
882
951
  const candidates = Object.keys(state.config.mcpServers)
883
952
  .filter(name => !isServerDisabled(state.config.mcpServers[name]))
884
953
  .map(name => ({ name, prefix: getServerPrefix(name, prefixMode) }))
@@ -909,11 +978,22 @@ export async function executeCall(
909
978
 
910
979
  if (!connected) continue;
911
980
  if (!prefixMatchedServer) prefixMatchedServer = configuredServer;
912
- toolMeta = findToolByName(state.toolMetadata.get(configuredServer), toolName);
913
- if (toolMeta) {
914
- serverName = configuredServer;
915
- break;
981
+ const metadata = state.toolMetadata.get(configuredServer);
982
+ const exactMatches = getToolMatches(metadata, toolName, true);
983
+ if (exactMatches.length > 1) return ambiguousToolResult("call", toolName);
984
+ if (exactMatches.length === 1) {
985
+ lazyExactMatches.push({ serverName: configuredServer, toolMeta: exactMatches[0]! });
986
+ continue;
916
987
  }
988
+ const fallbackMatches = getToolMatches(metadata, toolName, false);
989
+ if (fallbackMatches.length > 1) return ambiguousToolResult("call", toolName);
990
+ if (fallbackMatches.length === 1) lazyFallbackMatches.push({ serverName: configuredServer, toolMeta: fallbackMatches[0]! });
991
+ }
992
+ const lazyMatches = lazyExactMatches.length > 0 ? lazyExactMatches : lazyFallbackMatches;
993
+ if (lazyMatches.length > 1) return ambiguousToolResult("call", toolName);
994
+ if (lazyMatches.length === 1) {
995
+ serverName = lazyMatches[0]!.serverName;
996
+ toolMeta = lazyMatches[0]!.toolMeta;
917
997
  }
918
998
  }
919
999
 
@@ -1026,7 +1106,9 @@ export async function executeCall(
1026
1106
  notifyToolMetadataUpdated(state, serverName, "proxy-call-reconnect");
1027
1107
  markKeepAliveAfterConnect(state, serverName);
1028
1108
  updateStatusBar(state);
1029
- toolMeta = findToolByName(state.toolMetadata.get(serverName), toolName);
1109
+ const match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);
1110
+ if (match === "ambiguous") return ambiguousToolResult("call", toolName);
1111
+ toolMeta = match;
1030
1112
  if (!toolMeta) {
1031
1113
  const available = getToolNames(state, serverName);
1032
1114
  const hint = available.length > 0
@@ -1,6 +1,6 @@
1
1
  import type { McpExtensionState } from "./state.ts";
2
- import type { ToolMetadata } from "./types.ts";
3
- import { getServerPrefix, isServerDisabled } from "./types.ts";
2
+ import type { ServerEntry, ToolMetadata, ToolPrefix } from "./types.ts";
3
+ import { getServerPrefix, getToolNameCandidates, isServerDisabled, matchesToolPattern, resolveToolPrefix } from "./types.ts";
4
4
 
5
5
  /**
6
6
  * Shortest field token allowed to stem-match a longer query token.
@@ -14,6 +14,7 @@ const FIELD_WEIGHTS = {
14
14
  originalName: 10,
15
15
  server: 8,
16
16
  description: 5,
17
+ keywords: 5,
17
18
  } as const;
18
19
 
19
20
  export interface RankedToolMatch {
@@ -22,6 +23,36 @@ export interface RankedToolMatch {
22
23
  score: number;
23
24
  }
24
25
 
26
+ /**
27
+ * Resolve the configured searchKeywords entries that apply to a tool.
28
+ * Keys match by original name, prefixed name, or glob — the same candidate
29
+ * set includeTools/excludeTools use — and all matching entries are unioned.
30
+ */
31
+ export function resolveSearchKeywords(
32
+ definition: ServerEntry | undefined,
33
+ toolOriginalName: string,
34
+ serverName: string,
35
+ globalPrefix: ToolPrefix,
36
+ ): string[] {
37
+ const map = definition?.searchKeywords;
38
+ if (!map || typeof map !== "object" || Array.isArray(map)) return [];
39
+ const candidates = getToolNameCandidates(toolOriginalName, serverName, resolveToolPrefix(definition, globalPrefix));
40
+ const keywords: string[] = [];
41
+ const seen = new Set<string>();
42
+ for (const [pattern, values] of Object.entries(map)) {
43
+ if (!Array.isArray(values)) continue;
44
+ if (!matchesToolPattern(candidates, [pattern])) continue;
45
+ for (const value of values) {
46
+ if (typeof value !== "string") continue;
47
+ const trimmed = value.trim();
48
+ if (!trimmed || seen.has(trimmed)) continue;
49
+ seen.add(trimmed);
50
+ keywords.push(trimmed);
51
+ }
52
+ }
53
+ return keywords;
54
+ }
55
+
25
56
  export function normalizeSearchText(value: string): string {
26
57
  return value
27
58
  .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
@@ -33,7 +64,7 @@ export function tokenize(value: string): string[] {
33
64
  return normalizeSearchText(value).split(/[^a-z0-9]+/).filter(Boolean);
34
65
  }
35
66
 
36
- export function scoreToolMatch(tool: ToolMetadata, server: string, query: string): number | null {
67
+ export function scoreToolMatch(tool: ToolMetadata, server: string, query: string, keywords?: string[]): number | null {
37
68
  const normalizedQuery = normalizeSearchText(query).trim();
38
69
  const queryTokens = tokenize(query);
39
70
  if (queryTokens.length === 0) return null;
@@ -78,6 +109,43 @@ export function scoreToolMatch(tool: ToolMetadata, server: string, query: string
78
109
  }
79
110
  }
80
111
 
112
+ // Configured keywords are discrete phrases, so the phrase-level bonus is
113
+ // computed per phrase (best match wins) rather than on a joined string,
114
+ // which would phrase-match queries spanning two unrelated keywords.
115
+ if (keywords !== undefined && keywords.length > 0) {
116
+ const weight = FIELD_WEIGHTS.keywords;
117
+ const phrases = keywords.map(keyword => normalizeSearchText(keyword).trim()).filter(Boolean);
118
+ let phraseScore = 0;
119
+ for (const phrase of phrases) {
120
+ if (phrase === normalizedQuery) {
121
+ phraseScore = Math.max(phraseScore, weight * 14);
122
+ phraseMatched = true;
123
+ wholeFieldExact = true;
124
+ } else if (phrase.startsWith(normalizedQuery)) {
125
+ phraseScore = Math.max(phraseScore, weight * 9);
126
+ phraseMatched = true;
127
+ } else if (phrase.includes(normalizedQuery)) {
128
+ phraseScore = Math.max(phraseScore, weight * 6);
129
+ phraseMatched = true;
130
+ }
131
+ }
132
+ score += phraseScore;
133
+
134
+ const keywordTokens = phrases.flatMap(tokenize);
135
+ for (const token of queryTokens) {
136
+ if (keywordTokens.includes(token)) {
137
+ score += weight * 4;
138
+ matchedTokens.add(token);
139
+ } else if (keywordTokens.some(keywordToken => keywordToken.startsWith(token) || (keywordToken.length >= MIN_STEM_LENGTH && token.startsWith(keywordToken)))) {
140
+ score += weight * 2;
141
+ matchedTokens.add(token);
142
+ } else if (phrases.some(phrase => phrase.includes(token))) {
143
+ score += weight;
144
+ matchedTokens.add(token);
145
+ }
146
+ }
147
+ }
148
+
81
149
  const coverage = matchedTokens.size / queryTokens.length;
82
150
  if (!phraseMatched && (queryTokens.length <= 2 ? coverage !== 1 : coverage < 0.6)) return null;
83
151
 
@@ -88,13 +156,24 @@ export function scoreToolMatch(tool: ToolMetadata, server: string, query: string
88
156
  return score;
89
157
  }
90
158
 
91
- export function rankToolMatches(state: McpExtensionState, query: string, server?: string): RankedToolMatch[] {
159
+ export function rankToolMatches(
160
+ state: McpExtensionState,
161
+ query: string,
162
+ server?: string,
163
+ includeKeywords = true,
164
+ ): RankedToolMatch[] {
92
165
  const matches: RankedToolMatch[] = [];
166
+ const globalPrefix = state.config.settings?.toolPrefix ?? "server";
93
167
  for (const [serverName, metadata] of state.toolMetadata.entries()) {
94
168
  if (server && serverName !== server) continue;
95
- if (isServerDisabled(state.config.mcpServers[serverName])) continue;
169
+ const definition = state.config.mcpServers[serverName];
170
+ if (isServerDisabled(definition)) continue;
171
+ const hasKeywords = includeKeywords && definition?.searchKeywords !== undefined;
96
172
  for (const tool of metadata) {
97
- const score = scoreToolMatch(tool, serverName, query);
173
+ const keywords = hasKeywords
174
+ ? resolveSearchKeywords(definition, tool.originalName, serverName, globalPrefix)
175
+ : undefined;
176
+ const score = scoreToolMatch(tool, serverName, query, keywords);
98
177
  if (score !== null) matches.push({ server: serverName, tool, score });
99
178
  }
100
179
  }
@@ -123,5 +202,5 @@ export function rankSuggestions(state: McpExtensionState, name: string, limit: n
123
202
  .sort((a, b) => b.length - a.length)
124
203
  .map(candidate => name.slice(candidate.length + 1));
125
204
  const query = stripped[0] ?? name;
126
- return rankToolMatches(state, query).slice(0, limit).map(match => match.tool.name);
205
+ return rankToolMatches(state, query, undefined, false).slice(0, limit).map(match => match.tool.name);
127
206
  }
@@ -31,7 +31,7 @@ import { createJsonSchemaValidator } from "./json-schema-validator.ts";
31
31
  import { logger } from "./logger.ts";
32
32
  import { McpOAuthProvider } from "./mcp-oauth-provider.ts";
33
33
  import { extractOAuthConfig, supportsOAuth, type McpOAuthRuntime } from "./mcp-auth-flow.ts";
34
- import type { AuthStorageOptions } from "./mcp-auth.ts";
34
+ import { invalidateAuthEntryCache, type AuthStorageOptions } from "./mcp-auth.ts";
35
35
  import { registerSamplingHandler, type ServerSamplingConfig } from "./sampling-handler.ts";
36
36
  import {
37
37
  handleUrlElicitation,
@@ -39,6 +39,7 @@ import {
39
39
  type ServerElicitationConfig,
40
40
  } from "./elicitation-handler.ts";
41
41
  import {
42
+ interpolateEnvVars,
42
43
  resolveBearerToken,
43
44
  resolveCommandSecret,
44
45
  resolveCommandSecretsRecord,
@@ -129,8 +130,11 @@ export interface ServerConnection {
129
130
  lastUsedAt: number;
130
131
  inFlight: number;
131
132
  status: "connected" | "closed" | "needs-auth";
133
+ /** True once this needs-auth episode discarded the cached credential. */
134
+ credentialsInvalidated?: boolean;
132
135
  }
133
136
 
137
+
134
138
  type UiStreamListener = (serverName: string, notification: ServerStreamResultPatchNotification["params"]) => void;
135
139
  type MetadataListChangedListener = (serverName: string, reason: string) => void;
136
140
 
@@ -238,10 +242,12 @@ export class McpServerManager {
238
242
  return existing;
239
243
  }
240
244
 
245
+ const credentialsInvalidated = existing?.status === "needs-auth"
246
+ && existing.credentialsInvalidated === true;
241
247
  const generation = this.closeGenerations.get(name) ?? 0;
242
248
  const attemptController = new AbortController();
243
249
  const attemptSignal = combineAbortSignals(ownedSignal, attemptController.signal);
244
- const connectionAttempt = this.createConnection(name, definition, attemptSignal, ownedSignal);
250
+ const connectionAttempt = this.createConnection(name, definition, attemptSignal, ownedSignal, credentialsInvalidated);
245
251
  const promise = definition.url
246
252
  ? connectionAttempt.catch(async error => { throw await this.enrichHttpConnectionError(definition, error); })
247
253
  : connectionAttempt;
@@ -324,6 +330,7 @@ export class McpServerManager {
324
330
  definition: ServerDefinition,
325
331
  signal?: AbortSignal,
326
332
  requestSignal?: AbortSignal,
333
+ credentialsInvalidated = false,
327
334
  ): Promise<ServerConnection> {
328
335
  throwIfAborted(signal);
329
336
 
@@ -338,6 +345,7 @@ export class McpServerManager {
338
345
  let client: Client;
339
346
  let transport: Transport;
340
347
  let clientConnected = false;
348
+ let invalidated = credentialsInvalidated;
341
349
  let transportAlreadyTraced = false;
342
350
  let stderrTail: Buffer<ArrayBufferLike> = Buffer.alloc(0);
343
351
  const configuredTransports = [definition.command, definition.url, definition.socket]
@@ -351,7 +359,7 @@ export class McpServerManager {
351
359
  if (definition.command) {
352
360
  client = this.createClient(name, definition);
353
361
  let command = definition.command;
354
- let args = definition.args ?? [];
362
+ let args = (definition.args ?? []).map(interpolateEnvVars);
355
363
 
356
364
  if (command === "npx" || command === "npm") {
357
365
  const resolved = await resolveNpxBinary(command, args, signal);
@@ -387,9 +395,11 @@ export class McpServerManager {
387
395
  requestOptions,
388
396
  signal,
389
397
  traceObserver,
398
+ invalidated,
390
399
  );
391
400
  client = httpConnection.client;
392
401
  transport = httpConnection.transport;
402
+ invalidated = httpConnection.credentialsInvalidated;
393
403
  if (httpConnection.status === "needs-auth") {
394
404
  return {
395
405
  client,
@@ -401,6 +411,7 @@ export class McpServerManager {
401
411
  lastUsedAt: Date.now(),
402
412
  inFlight: 0,
403
413
  status: "needs-auth",
414
+ credentialsInvalidated: invalidated,
404
415
  };
405
416
  }
406
417
  clientConnected = true;
@@ -477,6 +488,10 @@ export class McpServerManager {
477
488
  // A cleanup failure remains a setup failure rather than being hidden
478
489
  // behind needs-auth.
479
490
  if (isUnauthorizedHttpError(error) && supportsOAuth(definition) && cleanupFailures.length === 0) {
491
+ if (!invalidated) {
492
+ invalidateAuthEntryCache(name);
493
+ invalidated = true;
494
+ }
480
495
  return {
481
496
  client,
482
497
  transport,
@@ -487,6 +502,7 @@ export class McpServerManager {
487
502
  lastUsedAt: Date.now(),
488
503
  inFlight: 0,
489
504
  status: "needs-auth",
505
+ credentialsInvalidated: invalidated,
490
506
  };
491
507
  }
492
508
 
@@ -694,7 +710,8 @@ export class McpServerManager {
694
710
  requestOptions: RequestOptions | undefined,
695
711
  signal?: AbortSignal,
696
712
  traceObserver?: McpTraceObserver,
697
- ): Promise<{ client: Client; transport: Transport; status: "connected" | "needs-auth" }> {
713
+ credentialsInvalidated = false,
714
+ ): Promise<{ client: Client; transport: Transport; status: "connected" | "needs-auth"; credentialsInvalidated: boolean }> {
698
715
  throwIfAborted(signal);
699
716
  const serverUrl = resolveServerUrl(definition)!;
700
717
  const url = new URL(serverUrl);
@@ -791,9 +808,10 @@ export class McpServerManager {
791
808
  // OAuth challenge; use SSE only for definitive endpoint incompatibility.
792
809
  // Agent Plugins set httpTransport, and their declared transport is used without fallback.
793
810
  let kind: "streamable-http" | "sse" = definition.httpTransport ?? "streamable-http";
811
+ let invalidated = credentialsInvalidated;
794
812
  for (;;) {
795
813
  const result = await attempt(kind);
796
- if (result.status === "connected") return result;
814
+ if (result.status === "connected") return { ...result, credentialsInvalidated: invalidated };
797
815
  if (result.error instanceof AggregateError
798
816
  && result.error.message === "MCP connection abort cleanup failed") {
799
817
  throw result.error;
@@ -806,7 +824,16 @@ export class McpServerManager {
806
824
  }
807
825
  if (isUnauthorizedHttpError(result.error)) {
808
826
  if (supportsOAuth(definition)) {
809
- return { client: result.client, transport: result.transport, status: "needs-auth" };
827
+ if (!invalidated) {
828
+ invalidateAuthEntryCache(serverName);
829
+ invalidated = true;
830
+ }
831
+ return {
832
+ client: result.client,
833
+ transport: result.transport,
834
+ status: "needs-auth",
835
+ credentialsInvalidated: invalidated,
836
+ };
810
837
  }
811
838
  throw result.error;
812
839
  }
@@ -850,6 +877,7 @@ export class McpServerManager {
850
877
  return { prompts, failed: false };
851
878
  } catch (error) {
852
879
  if (requestOptions?.signal?.aborted) throwIfAborted(requestOptions.signal);
880
+ if (isUnauthorizedHttpError(error)) throw error;
853
881
  const message = error instanceof Error ? error.message : String(error);
854
882
  logger.debug(`MCP: prompts/list failed: ${message}`);
855
883
  return { prompts: [], failed: true };
@@ -871,10 +899,11 @@ export class McpServerManager {
871
899
  } while (cursor);
872
900
 
873
901
  return allResources;
874
- } catch {
902
+ } catch (error) {
875
903
  if (requestOptions?.signal?.aborted) {
876
904
  throwIfAborted(requestOptions.signal);
877
905
  }
906
+ if (isUnauthorizedHttpError(error)) throw error;
878
907
  // The server advertises resources but the listing failed
879
908
  return [];
880
909
  }
@@ -20,10 +20,12 @@
20
20
  // many things other than "your session is gone"
21
21
  // - treat generic -32000/ConnectionClosed errors as session expiry
22
22
  // - treat AbortError/cancellation as a session failure
23
- import { ProtocolError, SdkHttpError } from "@modelcontextprotocol/client";
23
+ import { ProtocolError, SdkHttpError, UnauthorizedError } from "@modelcontextprotocol/client";
24
24
  import { logger } from "./logger.ts";
25
25
  import { throwIfAborted } from "./abort.ts";
26
26
  import { isServerDisabled, type McpConfig } from "./types.ts";
27
+ import { supportsOAuth } from "./mcp-auth-flow.ts";
28
+ import { invalidateAuthEntryCache } from "./mcp-auth.ts";
27
29
  import type { McpServerManager, ServerConnection } from "./server-manager.ts";
28
30
 
29
31
  /**
@@ -106,6 +108,11 @@ export async function withSessionRecovery<T>(
106
108
  try {
107
109
  return await fn(connection);
108
110
  } catch (err) {
111
+ const definition = deps.config.mcpServers[serverName];
112
+ if (definition && supportsOAuth(definition)
113
+ && (err instanceof UnauthorizedError || (err instanceof SdkHttpError && err.status === 401))) {
114
+ invalidateAuthEntryCache(serverName);
115
+ }
109
116
  if (!isTerminatedSession(err, hadSessionId)) {
110
117
  throw err;
111
118
  }
@@ -114,7 +121,6 @@ export async function withSessionRecovery<T>(
114
121
  // connection's definition, in case config changed since connect. If the
115
122
  // server was removed from config in the meantime there is nothing to
116
123
  // reconnect to, so surface the original error.
117
- const definition = deps.config.mcpServers[serverName];
118
124
  if (!definition) {
119
125
  throw err;
120
126
  }
@@ -24,19 +24,59 @@ export function isToolCallApprovalRequired(
24
24
  config: McpConfig,
25
25
  serverName: string,
26
26
  toolMeta: Pick<ToolMetadata, "originalName">,
27
+ toolMetadata?: ReadonlyMap<string, readonly ToolMetadata[]>,
27
28
  ): boolean {
28
29
  const definition = config.mcpServers[serverName];
29
- const approval = definition?.approveTools !== undefined
30
- ? definition.approveTools
31
- : config.settings?.approveTools;
30
+ const serverApproval = definition?.approveTools;
31
+ const approval = serverApproval !== undefined ? serverApproval : config.settings?.approveTools;
32
32
 
33
33
  if (approval === true) return true;
34
34
  if (!Array.isArray(approval) || approval.length === 0) return false;
35
35
 
36
36
  const prefix = resolveToolPrefix(definition, config.settings?.toolPrefix);
37
- return matchesToolPattern(
38
- getToolNameCandidates(toolMeta.originalName, serverName, prefix),
39
- approval,
37
+ const currentCandidates = getToolNameCandidates(toolMeta.originalName, serverName, prefix, false);
38
+ if (serverApproval !== undefined) {
39
+ if (matchesToolPattern(currentCandidates, approval)) return true;
40
+ if (!toolMetadata) return matchesToolPattern(getToolNameCandidates(toolMeta.originalName, serverName, prefix), approval);
41
+ const legacyCandidates = getToolNameCandidates(toolMeta.originalName, serverName, prefix);
42
+ const legacyEmittedName = [...currentCandidates].find(candidate => candidate !== toolMeta.originalName)?.replace(/-/g, "_");
43
+ if (legacyEmittedName) legacyCandidates.add(legacyEmittedName);
44
+ for (const candidate of currentCandidates) legacyCandidates.delete(candidate);
45
+ const otherCurrentCandidates = new Set<string>();
46
+ for (const tool of toolMetadata.get(serverName) ?? []) {
47
+ for (const candidate of getToolNameCandidates(tool.originalName, serverName, prefix, false)) {
48
+ otherCurrentCandidates.add(candidate);
49
+ }
50
+ }
51
+ for (const candidate of currentCandidates) otherCurrentCandidates.delete(candidate);
52
+ return approval.some(pattern =>
53
+ matchesToolPattern(legacyCandidates, [pattern])
54
+ && !matchesToolPattern(otherCurrentCandidates, [pattern]),
55
+ );
56
+ }
57
+
58
+
59
+ if (matchesToolPattern(currentCandidates, approval)) return true;
60
+ if (!toolMetadata) return false;
61
+
62
+ const legacyCandidates = getToolNameCandidates(toolMeta.originalName, serverName, prefix);
63
+ const legacyEmittedName = [...currentCandidates].find(candidate => candidate !== toolMeta.originalName)?.replace(/-/g, "_");
64
+ if (legacyEmittedName) legacyCandidates.add(legacyEmittedName);
65
+ for (const candidate of currentCandidates) legacyCandidates.delete(candidate);
66
+ const otherCurrentCandidates = new Set<string>();
67
+ for (const [name, metadata] of toolMetadata) {
68
+ const otherPrefix = resolveToolPrefix(config.mcpServers[name], config.settings?.toolPrefix);
69
+ for (const tool of metadata) {
70
+ for (const candidate of getToolNameCandidates(tool.originalName, name, otherPrefix, false)) {
71
+ otherCurrentCandidates.add(candidate);
72
+ }
73
+ }
74
+ }
75
+ for (const candidate of currentCandidates) otherCurrentCandidates.delete(candidate);
76
+
77
+ return approval.some(pattern =>
78
+ matchesToolPattern(legacyCandidates, [pattern])
79
+ && !matchesToolPattern(otherCurrentCandidates, [pattern]),
40
80
  );
41
81
  }
42
82
 
@@ -94,6 +134,7 @@ export async function ensureToolCallApproved(
94
134
  args: Record<string, unknown> | undefined,
95
135
  signal?: AbortSignal,
96
136
  origin: McpToolApprovalOrigin = toolMeta.resourceUri ? "resource" : "proxy",
137
+ approvalMetadata?: ReadonlyMap<string, readonly ToolMetadata[]>,
97
138
  ): Promise<ToolCallApprovalResult> {
98
139
  const cacheKey = `${serverName}\u0000${toolMeta.originalName}`;
99
140
  const approvedToolCalls = state.approvedToolCalls ??= new Map<string, true>();
@@ -109,7 +150,7 @@ export async function ensureToolCallApproved(
109
150
  }
110
151
  if (brokerDecision === "deny") return { ok: false, reason: "denied" };
111
152
 
112
- if (!isToolCallApprovalRequired(state.config, serverName, toolMeta)) {
153
+ if (!isToolCallApprovalRequired(state.config, serverName, toolMeta, approvalMetadata ?? state.toolMetadata)) {
113
154
  return { ok: true };
114
155
  }
115
156