@herbertgao/pi-extensions 2026.8.14 → 2026.9.0

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 (270) hide show
  1. package/README.md +19 -13
  2. package/THIRD_PARTY_NOTICES.md +26 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +8 -1
  4. package/node_modules/@czottmann/pi-automode/README.md +4 -0
  5. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +71 -9
  6. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  7. package/node_modules/@herbertgao/pi-bark/LICENSE +21 -0
  8. package/node_modules/@herbertgao/pi-bark/README.md +44 -0
  9. package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
  10. package/node_modules/@herbertgao/pi-bark/package.json +53 -0
  11. package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +20 -0
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +46 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +6 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +5 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +34 -30
  18. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  19. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  20. package/node_modules/@narumitw/pi-btw/README.md +129 -88
  21. package/node_modules/@narumitw/pi-btw/dist/index.ts +283 -59
  22. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  23. package/node_modules/@narumitw/pi-btw/package.json +5 -8
  24. package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
  25. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +264 -24
  26. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  27. package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
  28. package/node_modules/@narumitw/pi-btw/src/text.ts +18 -0
  29. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +5 -16
  30. package/node_modules/@narumitw/pi-caffeinate/LICENSE +21 -0
  31. package/node_modules/@narumitw/pi-caffeinate/README.md +224 -0
  32. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +1112 -0
  33. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +7 -0
  34. package/node_modules/@narumitw/pi-caffeinate/package.json +53 -0
  35. package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +811 -0
  36. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +153 -0
  37. package/node_modules/@narumitw/pi-caffeinate/src/index.ts +1 -0
  38. package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +43 -0
  39. package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +167 -0
  40. package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +202 -0
  41. package/node_modules/@tifan/pi-handoff/README.md +39 -13
  42. package/node_modules/@tifan/pi-handoff/package.json +2 -2
  43. package/node_modules/@tifan/pi-handoff/src/index.ts +166 -5
  44. package/node_modules/@tifan/pi-recap/README.md +1 -1
  45. package/node_modules/@tifan/pi-recap/package.json +2 -2
  46. package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
  47. package/node_modules/@tifan/pi-rename/README.md +42 -21
  48. package/node_modules/@tifan/pi-rename/package.json +1 -1
  49. package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
  50. package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
  51. package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
  52. package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
  53. package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
  54. package/node_modules/pi-lens/CHANGELOG.md +320 -0
  55. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
  56. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
  57. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
  58. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
  59. package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
  60. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
  61. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
  62. package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
  63. package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
  64. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
  66. package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
  67. package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
  68. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
  69. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
  70. package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
  71. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
  73. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
  74. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
  75. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
  76. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
  77. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  93. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
  94. package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
  95. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
  97. package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
  98. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
  99. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
  100. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
  101. package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
  102. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
  103. package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
  104. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
  105. package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
  106. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  107. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
  108. package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
  109. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  110. package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
  111. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
  112. package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
  113. package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
  114. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
  115. package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
  116. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
  117. package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
  118. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
  119. package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
  120. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
  121. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
  122. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  123. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
  124. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
  125. package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
  126. package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
  127. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
  128. package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
  129. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
  130. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
  131. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
  132. package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
  133. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
  134. package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
  135. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
  137. package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
  138. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
  139. package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
  140. package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
  141. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
  142. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
  143. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
  144. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
  145. package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
  146. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
  147. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
  148. package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
  149. package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
  150. package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
  151. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
  152. package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
  153. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
  154. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
  155. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
  156. package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
  157. package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
  158. package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
  159. package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
  160. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  161. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
  162. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
  163. package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
  164. package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
  165. package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
  166. package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
  167. package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
  168. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
  169. package/node_modules/pi-lens/dist/index.js +11243 -6651
  170. package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
  171. package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
  172. package/node_modules/pi-lens/dist/mcp/server.js +6 -1
  173. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
  174. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
  175. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  176. package/node_modules/pi-lens/docs/agent-guide.md +4 -2
  177. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
  178. package/node_modules/pi-lens/docs/astplayground.md +26 -17
  179. package/node_modules/pi-lens/package.json +5 -1
  180. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  181. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  182. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  183. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  184. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  185. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  186. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  187. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  188. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  189. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  190. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  191. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  192. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  193. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  194. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  195. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  196. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  197. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  198. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  199. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  200. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  201. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  202. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  203. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  204. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  205. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  206. package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  207. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  208. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  209. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  210. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  211. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  212. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  215. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  216. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  217. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  218. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  219. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  220. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  221. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  222. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  223. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
  224. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  225. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  226. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  227. package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  228. package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  232. package/node_modules/pi-mcp-adapter/CHANGELOG.md +42 -0
  233. package/node_modules/pi-mcp-adapter/README.md +15 -15
  234. package/node_modules/pi-mcp-adapter/commands.ts +59 -22
  235. package/node_modules/pi-mcp-adapter/config.ts +18 -4
  236. package/node_modules/pi-mcp-adapter/direct-tools.ts +26 -6
  237. package/node_modules/pi-mcp-adapter/dist/config.d.ts +4 -0
  238. package/node_modules/pi-mcp-adapter/dist/config.js +13 -4
  239. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  240. package/node_modules/pi-mcp-adapter/dist/types.d.ts +12 -4
  241. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  242. package/node_modules/pi-mcp-adapter/error-signal.ts +15 -4
  243. package/node_modules/pi-mcp-adapter/errors.ts +141 -0
  244. package/node_modules/pi-mcp-adapter/host-html-template.ts +58 -5
  245. package/node_modules/pi-mcp-adapter/index.ts +2 -3
  246. package/node_modules/pi-mcp-adapter/init.ts +5 -0
  247. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +105 -32
  248. package/node_modules/pi-mcp-adapter/mcp-panel.ts +30 -9
  249. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +66 -28
  250. package/node_modules/pi-mcp-adapter/mcp-status.ts +2 -0
  251. package/node_modules/pi-mcp-adapter/package.json +2 -1
  252. package/node_modules/pi-mcp-adapter/proxy-modes.ts +88 -25
  253. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +217 -0
  254. package/node_modules/pi-mcp-adapter/server-manager.ts +343 -6
  255. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +1 -0
  256. package/node_modules/pi-mcp-adapter/types.ts +19 -4
  257. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +18 -2
  258. package/node_modules/pi-mcp-adapter/ui-server.ts +179 -54
  259. package/node_modules/pi-mcp-adapter/ui-session.ts +28 -8
  260. package/node_modules/pi-web-access/CHANGELOG.md +20 -0
  261. package/node_modules/pi-web-access/README.md +15 -2
  262. package/node_modules/pi-web-access/extract.ts +57 -12
  263. package/node_modules/pi-web-access/github-extract.ts +47 -3
  264. package/node_modules/pi-web-access/index.ts +1 -1
  265. package/node_modules/pi-web-access/package.json +1 -1
  266. package/node_modules/pi-web-access/page-query.ts +61 -8
  267. package/package.json +21 -17
  268. package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
  269. package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
  270. package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
@@ -11,6 +11,8 @@ import {
11
11
  type GetPromptResult,
12
12
  type ListToolsResult,
13
13
  type ReadResourceResult,
14
+ type McpSubscription,
15
+ type SubscriptionFilter,
14
16
  type CacheableRequestOptions,
15
17
  type RequestOptions,
16
18
  type UrlElicitationRequiredError,
@@ -28,15 +30,17 @@ import {
28
30
  type ServerStreamResultPatchNotification,
29
31
  type Transport,
30
32
  type McpTraceSettings,
33
+ type McpListenState,
31
34
  SERVER_STREAM_RESULT_PATCH_METHOD,
32
35
  serverStreamResultPatchNotificationSchema,
33
36
  } from "./types.ts";
34
37
  import { resolveNpxBinary } from "./npx-resolver.ts";
35
38
  import { createJsonSchemaValidator } from "./json-schema-validator.ts";
36
39
  import { logger } from "./logger.ts";
40
+ import { RESOURCE_MIME_TYPE } from "./ui-app-bridge-helpers.ts";
37
41
  import { McpOAuthProvider } from "./mcp-oauth-provider.ts";
38
42
  import { extractOAuthConfig, supportsOAuth, type McpOAuthRuntime } from "./mcp-auth-flow.ts";
39
- import { invalidateAuthEntryCache, type AuthStorageOptions } from "./mcp-auth.ts";
43
+ import { inspectAuthForUrl, invalidateAuthEntryCache, type AuthStorageOptions } from "./mcp-auth.ts";
40
44
  import { getBearerTokenForUrl } from "./mcp-bearer-store.ts";
41
45
  import { registerSamplingHandler, type ServerSamplingConfig } from "./sampling-handler.ts";
42
46
  import {
@@ -71,6 +75,7 @@ const abortCleanupPromises = new WeakMap<object, Promise<void>>();
71
75
  type HttpAuthProviderState =
72
76
  | { status: "disabled" }
73
77
  | { status: "implicit-deferred" }
78
+ | { status: "implicit-stored"; provider: McpOAuthProvider }
74
79
  | { status: "explicit"; provider: McpOAuthProvider }
75
80
  | { status: "implicit-challenged"; provider: McpOAuthProvider };
76
81
 
@@ -141,6 +146,18 @@ export interface ServerConnection {
141
146
  lastUsedAt: number;
142
147
  inFlight: number;
143
148
  status: "connected" | "closed" | "needs-auth";
149
+ /** Catalog subscription health, tracked independently from transport health. */
150
+ listenState: McpListenState;
151
+ listenSubscription?: McpSubscription;
152
+ /** Last requested filter; the server's honored filter may be a subset. */
153
+ listenFilter?: SubscriptionFilter;
154
+ /** True when recovery has an active listen but could not confirm every catalog list. */
155
+ listenCatalogStale?: boolean;
156
+ listenPromise?: Promise<void>;
157
+ listenRetryAfter?: number;
158
+ listenStopped?: boolean;
159
+ recentResourceUris?: Map<string, number>;
160
+ resourceReadRefreshUris?: Set<string>;
144
161
  /** True once this needs-auth episode discarded the cached credential. */
145
162
  credentialsInvalidated?: boolean;
146
163
  }
@@ -148,6 +165,8 @@ export interface ServerConnection {
148
165
 
149
166
  type UiStreamListener = (serverName: string, notification: ServerStreamResultPatchNotification["params"]) => void;
150
167
  type MetadataListChangedListener = (serverName: string, reason: string) => void;
168
+ type ListenStateChangedListener = (serverName: string, state: McpListenState) => void;
169
+ type ResourceUpdatedListener = (serverName: string, uri: string) => void;
151
170
 
152
171
  export type ToolRefreshResult = "updated" | "unchanged" | "superseded" | "refresh-timeout";
153
172
 
@@ -155,6 +174,9 @@ type ToolListCacheHints = Partial<Pick<ListToolsResult, "ttlMs" | "cacheScope">>
155
174
  type ToolListResult = { tools: McpTool[]; hints?: ToolListCacheHints };
156
175
 
157
176
  const KEEP_ALIVE_REFRESH_TIMEOUT_MS = 5_000;
177
+ const LISTEN_RETRY_DELAY_MS = 5_000;
178
+ const RECENT_RESOURCE_TTL_MS = 10 * 60_000;
179
+ const MAX_RESOURCE_SUBSCRIPTIONS = 32;
158
180
 
159
181
  export function isTransientHttpConnectError(error: unknown): boolean {
160
182
  let current: unknown = error;
@@ -172,6 +194,12 @@ export class McpServerManager {
172
194
  private uiStreamListeners = new Map<string, UiStreamListener>();
173
195
  private samplingConfig: ServerSamplingConfig | undefined;
174
196
  private metadataListChangedListener: MetadataListChangedListener | undefined;
197
+ private listenStateChangedListener: ListenStateChangedListener | undefined;
198
+ private resourceUpdatedListeners = new Map<string, {
199
+ serverName: string;
200
+ uri: string;
201
+ listener: ResourceUpdatedListener;
202
+ }>();
175
203
  private pendingMetadataPublications = new Map<
176
204
  string,
177
205
  { connection: ServerConnection; reason: string }
@@ -200,6 +228,10 @@ export class McpServerManager {
200
228
  this.metadataListChangedListener = listener;
201
229
  }
202
230
 
231
+ setListenStateChangedListener(listener: ListenStateChangedListener | undefined): void {
232
+ this.listenStateChangedListener = listener;
233
+ }
234
+
203
235
  publishMetadataChanged(
204
236
  name: string,
205
237
  expectedConnection: ServerConnection,
@@ -312,6 +344,10 @@ export class McpServerManager {
312
344
  throw new Error(`MCP connection for ${name} was closed while connecting`);
313
345
  }
314
346
  this.connections.set(name, connection);
347
+ this.watchListenSubscription(name, connection, connection.listenSubscription);
348
+ if ([...this.resourceUpdatedListeners.values()].some(registration => registration.serverName === name)) {
349
+ void this.ensureListen(name, connection);
350
+ }
315
351
  return connection;
316
352
  } finally {
317
353
  if (this.connectPromises.get(name) === promise) this.connectPromises.delete(name);
@@ -372,6 +408,8 @@ export class McpServerManager {
372
408
  return "superseded";
373
409
  }
374
410
 
411
+ await this.ensureListen(name, expectedConnection);
412
+
375
413
  const requestOptions = this.buildRequestOptions(expectedConnection.definition, signal);
376
414
  const timeout = Math.min(requestOptions?.timeout ?? KEEP_ALIVE_REFRESH_TIMEOUT_MS, KEEP_ALIVE_REFRESH_TIMEOUT_MS);
377
415
  const healthOptions = {
@@ -456,6 +494,250 @@ export class McpServerManager {
456
494
  }
457
495
  }
458
496
 
497
+ private setListenState(name: string, connection: ServerConnection, state: McpListenState): void {
498
+ if (connection.listenState === state) return;
499
+ connection.listenState = state;
500
+ if (this.connections.get(name) === connection) {
501
+ try {
502
+ this.listenStateChangedListener?.(name, state);
503
+ } catch {
504
+ // Status consumers must not interrupt listen lifecycle recovery.
505
+ }
506
+ }
507
+ }
508
+
509
+ private catalogListenFilter(connection: ServerConnection): SubscriptionFilter {
510
+ const capabilities = connection.client.getServerCapabilities?.();
511
+ return {
512
+ ...(capabilities?.tools?.listChanged ? { toolsListChanged: true } : {}),
513
+ ...(capabilities?.prompts?.listChanged ? { promptsListChanged: true } : {}),
514
+ ...(capabilities?.resources?.listChanged ? { resourcesListChanged: true } : {}),
515
+ };
516
+ }
517
+
518
+ private currentListenFilter(name: string, connection: ServerConnection): SubscriptionFilter {
519
+ const now = Date.now();
520
+ const recent = connection.recentResourceUris;
521
+ if (recent) {
522
+ for (const [uri, touchedAt] of recent) {
523
+ if (now - touchedAt > RECENT_RESOURCE_TTL_MS) recent.delete(uri);
524
+ }
525
+ }
526
+ const openResourceUris = [...new Set(
527
+ [...this.resourceUpdatedListeners.values()]
528
+ .filter(registration => registration.serverName === name)
529
+ .map(registration => registration.uri),
530
+ )].slice(-MAX_RESOURCE_SUBSCRIPTIONS);
531
+ const openSet = new Set(openResourceUris);
532
+ const recentSlots = MAX_RESOURCE_SUBSCRIPTIONS - openResourceUris.length;
533
+ const recentResourceUris = recentSlots > 0
534
+ ? [...(recent?.keys() ?? [])].filter(uri => !openSet.has(uri)).slice(-recentSlots)
535
+ : [];
536
+ const resourceSubscriptions = [...openResourceUris, ...recentResourceUris];
537
+ return {
538
+ ...this.catalogListenFilter(connection),
539
+ ...(resourceSubscriptions.length > 0 ? { resourceSubscriptions } : {}),
540
+ };
541
+ }
542
+
543
+ private watchListenSubscription(
544
+ name: string,
545
+ connection: ServerConnection,
546
+ subscription: McpSubscription | undefined,
547
+ ): void {
548
+ if (!subscription) return;
549
+ void subscription.closed.then(cause => {
550
+ if (
551
+ this.stopped ||
552
+ this.connections.get(name) !== connection ||
553
+ connection.status !== "connected" ||
554
+ connection.listenSubscription !== subscription
555
+ ) return;
556
+ if (cause === "remote") {
557
+ const staleUris = connection.listenFilter?.resourceSubscriptions ?? [];
558
+ if (staleUris.length > 0) connection.resourceReadRefreshUris = new Set(staleUris);
559
+ connection.listenCatalogStale = true;
560
+ connection.listenRetryAfter = Date.now();
561
+ this.setListenState(name, connection, "dropped");
562
+ } else if (cause === "graceful" || connection.listenState !== "re-establishing") {
563
+ connection.listenStopped = true;
564
+ this.setListenState(name, connection, "not-listening");
565
+ }
566
+ });
567
+ }
568
+
569
+ /** Quietly repairs a modern catalog listen at an existing activity boundary. */
570
+ async ensureListen(name: string, expectedConnection: ServerConnection): Promise<void> {
571
+ if (
572
+ this.stopped ||
573
+ this.connections.get(name) !== expectedConnection ||
574
+ expectedConnection.status !== "connected" ||
575
+ expectedConnection.listenStopped ||
576
+ expectedConnection.client.getProtocolEra?.() !== "modern"
577
+ ) return;
578
+
579
+ const filter = this.currentListenFilter(name, expectedConnection);
580
+ if (Object.keys(filter).length === 0) {
581
+ this.setListenState(name, expectedConnection, "not-listening");
582
+ return;
583
+ }
584
+ if (expectedConnection.listenPromise) {
585
+ await expectedConnection.listenPromise;
586
+ return this.ensureListen(name, expectedConnection);
587
+ }
588
+ if ((expectedConnection.listenRetryAfter ?? 0) > Date.now()) return;
589
+
590
+ const sameFilter = isDeepStrictEqual(expectedConnection.listenFilter, filter);
591
+ if (expectedConnection.listenState === "active" && sameFilter) {
592
+ if (!expectedConnection.listenCatalogStale) return;
593
+ const attempt = (async () => {
594
+ this.setListenState(name, expectedConnection, "re-establishing");
595
+ const confirmed = await this.reconcileCatalogAfterListen(name, expectedConnection, this.buildRequestOptions(expectedConnection.definition));
596
+ if (!confirmed) expectedConnection.listenRetryAfter = Date.now() + LISTEN_RETRY_DELAY_MS;
597
+ this.setListenState(name, expectedConnection, "active");
598
+ })().finally(() => {
599
+ if (expectedConnection.listenPromise === attempt) delete expectedConnection.listenPromise;
600
+ });
601
+ expectedConnection.listenPromise = attempt;
602
+ return attempt;
603
+ }
604
+
605
+ const attempt = (async () => {
606
+ const recoverDroppedListen = expectedConnection.listenState === "dropped";
607
+ this.setListenState(name, expectedConnection, "re-establishing");
608
+ const previous = expectedConnection.listenSubscription;
609
+ if (previous) await previous.close().catch(() => {});
610
+ if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return;
611
+ try {
612
+ const requestOptions = this.buildRequestOptions(expectedConnection.definition);
613
+ const subscription = await expectedConnection.client.listen(
614
+ filter,
615
+ {
616
+ ...requestOptions,
617
+ timeout: Math.min(requestOptions?.timeout ?? KEEP_ALIVE_REFRESH_TIMEOUT_MS, KEEP_ALIVE_REFRESH_TIMEOUT_MS),
618
+ },
619
+ );
620
+ if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") {
621
+ await subscription.close().catch(() => {});
622
+ return;
623
+ }
624
+ expectedConnection.listenSubscription = subscription;
625
+ expectedConnection.listenFilter = filter;
626
+ delete expectedConnection.listenStopped;
627
+ delete expectedConnection.listenRetryAfter;
628
+ this.watchListenSubscription(name, expectedConnection, subscription);
629
+ const confirmed = recoverDroppedListen
630
+ ? await this.reconcileCatalogAfterListen(name, expectedConnection, requestOptions)
631
+ : true;
632
+ if (!confirmed) expectedConnection.listenRetryAfter = Date.now() + LISTEN_RETRY_DELAY_MS;
633
+ this.setListenState(name, expectedConnection, "active");
634
+ } catch (error) {
635
+ if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return;
636
+ if (expectedConnection.listenSubscription) await expectedConnection.listenSubscription.close().catch(() => {});
637
+ expectedConnection.listenRetryAfter = Date.now() + LISTEN_RETRY_DELAY_MS;
638
+ this.setListenState(name, expectedConnection, "dropped");
639
+ logger.debug(`MCP: catalog listen repair failed for ${name}: ${error instanceof Error ? error.message : String(error)}`);
640
+ }
641
+ })().finally(() => {
642
+ if (expectedConnection.listenPromise === attempt) delete expectedConnection.listenPromise;
643
+ });
644
+ expectedConnection.listenPromise = attempt;
645
+ return attempt;
646
+ }
647
+
648
+ private async reconcileCatalogAfterListen(
649
+ name: string,
650
+ expectedConnection: ServerConnection,
651
+ requestOptions?: CacheableRequestOptions,
652
+ ): Promise<boolean> {
653
+ const timeout = Math.min(requestOptions?.timeout ?? KEEP_ALIVE_REFRESH_TIMEOUT_MS, KEEP_ALIVE_REFRESH_TIMEOUT_MS);
654
+ const refreshSignal = combineAbortSignals(requestOptions?.signal, AbortSignal.timeout(timeout));
655
+ const refreshOptions: CacheableRequestOptions = {
656
+ ...requestOptions,
657
+ timeout,
658
+ cacheMode: "refresh",
659
+ ...(refreshSignal ? { signal: refreshSignal } : {}),
660
+ };
661
+ const [toolResult, resources, promptResult] = await Promise.allSettled([
662
+ this.fetchAllTools(expectedConnection.client, refreshOptions),
663
+ this.fetchAllResources(expectedConnection.client, refreshOptions, true),
664
+ this.fetchAllPrompts(expectedConnection.client, refreshOptions),
665
+ ]);
666
+ if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return false;
667
+
668
+ const nextTools = toolResult.status === "fulfilled" ? toolResult.value : undefined;
669
+ const nextResources = resources.status === "fulfilled" ? resources.value : undefined;
670
+ const nextPrompts = promptResult.status === "fulfilled" && !promptResult.value.failed ? promptResult.value : undefined;
671
+ const confirmed = nextTools !== undefined && nextResources !== undefined && nextPrompts !== undefined;
672
+ const changed = (nextTools !== undefined && (
673
+ !isDeepStrictEqual(expectedConnection.tools, nextTools.tools) ||
674
+ !isDeepStrictEqual(expectedConnection.toolListHints, nextTools.hints)
675
+ )) ||
676
+ (nextResources !== undefined && !isDeepStrictEqual(expectedConnection.resources, nextResources)) ||
677
+ (nextPrompts !== undefined && (
678
+ !isDeepStrictEqual(expectedConnection.prompts, nextPrompts.prompts) ||
679
+ expectedConnection.promptDiscoveryFailed !== false
680
+ ));
681
+ if (!changed) {
682
+ this.retryPendingMetadataPublication(name, expectedConnection);
683
+ if (confirmed) delete expectedConnection.listenCatalogStale;
684
+ else expectedConnection.listenCatalogStale = true;
685
+ return confirmed;
686
+ }
687
+
688
+ if (nextTools !== undefined) {
689
+ expectedConnection.tools = nextTools.tools;
690
+ expectedConnection.toolListHints = nextTools.hints;
691
+ expectedConnection.toolsRevision = (expectedConnection.toolsRevision ?? 0) + 1;
692
+ }
693
+ if (nextResources !== undefined) expectedConnection.resources = nextResources;
694
+ if (nextPrompts !== undefined) {
695
+ expectedConnection.prompts = nextPrompts.prompts;
696
+ expectedConnection.promptDiscoveryFailed = false;
697
+ }
698
+ if (confirmed) delete expectedConnection.listenCatalogStale;
699
+ else expectedConnection.listenCatalogStale = true;
700
+ this.metadataListChangedListener?.(name, "listen-recovered");
701
+ this.pendingMetadataPublications.delete(name);
702
+ return confirmed;
703
+ }
704
+
705
+ async prepareResourceUse(name: string, uri: string, expectedConnection: ServerConnection): Promise<boolean> {
706
+ if (this.connections.get(name) !== expectedConnection || expectedConnection.status !== "connected") return false;
707
+ const refreshRead = expectedConnection.listenState === "dropped" ||
708
+ expectedConnection.listenState === "re-establishing" ||
709
+ expectedConnection.resourceReadRefreshUris?.has(uri) === true;
710
+ const recent = expectedConnection.recentResourceUris ?? new Map<string, number>();
711
+ recent.delete(uri);
712
+ recent.set(uri, Date.now());
713
+ while (recent.size > MAX_RESOURCE_SUBSCRIPTIONS) {
714
+ const oldest = recent.keys().next().value as string | undefined;
715
+ if (oldest === undefined) break;
716
+ recent.delete(oldest);
717
+ }
718
+ expectedConnection.recentResourceUris = recent;
719
+ await this.ensureListen(name, expectedConnection);
720
+ expectedConnection.resourceReadRefreshUris?.delete(uri);
721
+ return refreshRead;
722
+ }
723
+
724
+ registerResourceUpdatedListener(
725
+ token: string,
726
+ serverName: string,
727
+ uri: string,
728
+ listener: ResourceUpdatedListener,
729
+ ): void {
730
+ this.removeResourceUpdatedListener(token);
731
+ this.resourceUpdatedListeners.set(token, { serverName, uri, listener });
732
+ const connection = this.connections.get(serverName);
733
+ if (!connection || connection.status !== "connected") return;
734
+ void this.prepareResourceUse(serverName, uri, connection);
735
+ }
736
+
737
+ removeResourceUpdatedListener(token: string): void {
738
+ this.resourceUpdatedListeners.delete(token);
739
+ }
740
+
459
741
  private async doReconnect(
460
742
  name: string,
461
743
  definition: ServerDefinition,
@@ -570,6 +852,7 @@ export class McpServerManager {
570
852
  lastUsedAt: Date.now(),
571
853
  inFlight: 0,
572
854
  status: "needs-auth",
855
+ listenState: "disconnected",
573
856
  credentialsInvalidated: invalidated,
574
857
  };
575
858
  }
@@ -593,6 +876,8 @@ export class McpServerManager {
593
876
  this.attachAdapterNotificationHandlers(name, client);
594
877
 
595
878
  const instructions = client.getInstructions?.();
879
+ const protocolEra = client.getProtocolEra?.();
880
+ const autoOpenedSubscription = client.autoOpenedSubscription;
596
881
  const connection: ServerConnection = {
597
882
  client,
598
883
  transport,
@@ -605,7 +890,16 @@ export class McpServerManager {
605
890
  lastUsedAt: Date.now(),
606
891
  inFlight: 0,
607
892
  status: "connected",
893
+ listenState: protocolEra === "modern"
894
+ ? autoOpenedSubscription
895
+ ? "active"
896
+ : "not-listening"
897
+ : "legacy",
898
+ ...(autoOpenedSubscription ? {
899
+ listenSubscription: autoOpenedSubscription,
900
+ } : {}),
608
901
  };
902
+ if (autoOpenedSubscription) connection.listenFilter = this.catalogListenFilter(connection);
609
903
 
610
904
  // Reflect the SDK's own close signal in connection status, guarded by
611
905
  // identity so a stale connection's late close can never clobber a fresh
@@ -663,6 +957,7 @@ export class McpServerManager {
663
957
  lastUsedAt: Date.now(),
664
958
  inFlight: 0,
665
959
  status: "needs-auth",
960
+ listenState: "disconnected",
666
961
  credentialsInvalidated: invalidated,
667
962
  };
668
963
  }
@@ -725,6 +1020,11 @@ export class McpServerManager {
725
1020
 
726
1021
  private buildClientCapabilities() {
727
1022
  return {
1023
+ extensions: {
1024
+ "io.modelcontextprotocol/ui": {
1025
+ mimeTypes: [RESOURCE_MIME_TYPE],
1026
+ },
1027
+ },
728
1028
  ...(this.samplingConfig ? { sampling: {} } : {}),
729
1029
  ...(this.elicitationConfig
730
1030
  ? {
@@ -929,11 +1229,26 @@ export class McpServerManager {
929
1229
  this.oauthRuntime?.signal,
930
1230
  );
931
1231
 
932
- // Explicit OAuth checks secure storage immediately. Implicit OAuth defers
933
- // provider construction until the server proves authentication is needed.
1232
+ // Explicit OAuth checks secure storage immediately. Implicit OAuth keeps
1233
+ // anonymous servers provider-free unless URL-bound credentials are already
1234
+ // stored, so an unavailable credential store does not break anonymous use.
1235
+ let implicitStoredAuth: ReturnType<typeof inspectAuthForUrl> | undefined;
1236
+ if (definition.auth === undefined && supportsOAuth(definition)) {
1237
+ try {
1238
+ implicitStoredAuth = inspectAuthForUrl(serverName, serverUrl, this.authStorageOptions);
1239
+ } catch {
1240
+ // Implicit preflight is opportunistic; malformed records must not block
1241
+ // an otherwise anonymous-capable server from connecting.
1242
+ }
1243
+ }
1244
+ const hasImplicitStoredTokens = implicitStoredAuth?.status === "present"
1245
+ && implicitStoredAuth.entry.tokens !== undefined;
1246
+ if (hasImplicitStoredTokens) invalidateAuthEntryCache(serverName);
934
1247
  let authState: HttpAuthProviderState = supportsOAuth(definition)
935
1248
  ? definition.auth === undefined
936
- ? { status: "implicit-deferred" }
1249
+ ? hasImplicitStoredTokens
1250
+ ? { status: "implicit-stored", provider: createAuthProvider() }
1251
+ : { status: "implicit-deferred" }
937
1252
  : { status: "explicit", provider: createAuthProvider() }
938
1253
  : { status: "disabled" };
939
1254
 
@@ -1076,7 +1391,7 @@ export class McpServerManager {
1076
1391
  }
1077
1392
  }
1078
1393
 
1079
- private async fetchAllResources(client: Client, requestOptions?: RequestOptions): Promise<McpResource[]> {
1394
+ private async fetchAllResources(client: Client, requestOptions?: RequestOptions, strict = false): Promise<McpResource[]> {
1080
1395
  const capabilities = client.getServerCapabilities?.();
1081
1396
  if (!capabilities?.resources) return [];
1082
1397
 
@@ -1096,12 +1411,27 @@ export class McpServerManager {
1096
1411
  throwIfAborted(requestOptions.signal);
1097
1412
  }
1098
1413
  if (isUnauthorizedHttpError(error)) throw error;
1414
+ if (strict) throw error;
1099
1415
  // The server advertises resources but the listing failed
1100
1416
  return [];
1101
1417
  }
1102
1418
  }
1103
1419
 
1104
1420
  private attachAdapterNotificationHandlers(serverName: string, client: Client): void {
1421
+ client.setNotificationHandler("notifications/resources/updated", notification => {
1422
+ const uri = notification.params.uri;
1423
+ const connection = this.connections.get(serverName);
1424
+ if (!connection || connection.client !== client || connection.status !== "connected") return;
1425
+ for (const registration of this.resourceUpdatedListeners.values()) {
1426
+ if (registration.serverName === serverName && registration.uri === uri) {
1427
+ try {
1428
+ registration.listener(serverName, uri);
1429
+ } catch {
1430
+ // One UI listener must not block resource invalidation for others.
1431
+ }
1432
+ }
1433
+ }
1434
+ });
1105
1435
  client.setNotificationHandler(
1106
1436
  SERVER_STREAM_RESULT_PATCH_METHOD,
1107
1437
  { params: serverStreamResultPatchNotificationSchema.shape.params },
@@ -1134,6 +1464,7 @@ export class McpServerManager {
1134
1464
  try {
1135
1465
  this.touch(name);
1136
1466
  this.incrementInFlight(name);
1467
+ await this.ensureListen(name, connection);
1137
1468
  return await connection.client.getPrompt(
1138
1469
  { name: promptName, ...(args ? { arguments: args } : {}) },
1139
1470
  this.getRequestOptions(name, signal),
@@ -1156,7 +1487,12 @@ export class McpServerManager {
1156
1487
  try {
1157
1488
  this.touch(name);
1158
1489
  this.incrementInFlight(name);
1159
- return await connection.client.readResource({ uri }, this.getRequestOptions(name, signal));
1490
+ const refreshRead = await this.prepareResourceUse(name, uri, connection);
1491
+ const requestOptions = this.getRequestOptions(name, signal);
1492
+ return await connection.client.readResource(
1493
+ { uri },
1494
+ refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions,
1495
+ );
1160
1496
  } finally {
1161
1497
  this.decrementInFlight(name);
1162
1498
  this.touch(name);
@@ -1229,6 +1565,7 @@ export class McpServerManager {
1229
1565
  .flatMap(result => result.status === "rejected" ? [result.reason] : [])
1230
1566
  .filter(error => this.containsCleanupFailure(error));
1231
1567
  this.uiStreamListeners.clear();
1568
+ this.resourceUpdatedListeners.clear();
1232
1569
  this.acceptedUrlElicitations.clear();
1233
1570
  this.pendingMetadataPublications.clear();
1234
1571
  this.samplingConfig = undefined;
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: mcp-scripting
3
3
  description: Write mcpScript JavaScript for discovering, inspecting, and calling MCP tools.
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # MCP scripting
@@ -26,6 +26,14 @@ export type McpServerRuntimeStatus =
26
26
  | "not-connected"
27
27
  | "disabled";
28
28
 
29
+ export type McpListenState =
30
+ | "active"
31
+ | "dropped"
32
+ | "re-establishing"
33
+ | "legacy"
34
+ | "not-listening"
35
+ | "disconnected";
36
+
29
37
  export interface McpServerStatusSnapshot {
30
38
  readonly name: string;
31
39
  readonly status: McpServerRuntimeStatus;
@@ -33,6 +41,8 @@ export interface McpServerStatusSnapshot {
33
41
  readonly resourceCount?: number;
34
42
  readonly failedAgoSeconds?: number;
35
43
  readonly disabled: boolean;
44
+ readonly listenState: McpListenState;
45
+ readonly catalogStale?: boolean;
36
46
  }
37
47
 
38
48
  export interface McpStatusSnapshot {
@@ -173,6 +183,9 @@ export type UiDisplayMode = "inline" | "fullscreen" | "pip";
173
183
  export interface UiServerHandle {
174
184
  url: string;
175
185
  port: number;
186
+ /** URL of the second-origin MCP Apps sandbox proxy. */
187
+ proxyUrl: string;
188
+ proxyPort: number;
176
189
  sessionToken: string;
177
190
  serverName: string;
178
191
  toolName: string;
@@ -183,6 +196,7 @@ export interface UiServerHandle {
183
196
  sendToolResult: (result: CallToolResult) => void;
184
197
  sendResultPatch: (result: CallToolResult) => void;
185
198
  sendToolCancelled: (reason: string) => void;
199
+ sendResourceUpdated: (uri: string) => void;
186
200
  sendHostContext: (context: UiHostContext) => void;
187
201
  /** Get accumulated messages from this session */
188
202
  getSessionMessages: () => UiSessionMessages;
@@ -378,7 +392,7 @@ export interface OAuthConfig {
378
392
  scope?: string;
379
393
  /** Extra authorization URL parameters for provider-specific extensions. Flow-owned parameters cannot be overridden. */
380
394
  authorizationParams?: Record<string, string>;
381
- /** Exact authorization-code redirect URI for pre-registered clients */
395
+ /** Exact authorization-code redirect URI for pre-registered clients. HTTPS redirects use manual callback URL completion. */
382
396
  redirectUri?: string;
383
397
  /** Client display name for dynamic registration */
384
398
  clientName?: string;
@@ -564,9 +578,8 @@ export interface McpSettings {
564
578
  approveTools?: boolean | string[];
565
579
  disableProxyTool?: boolean;
566
580
  /** Freeze direct-tool registration after the initial sync. Automatic metadata updates
567
- * (reconnects, lazy-connect, tool-list-changed) won't rebuild the system prompt,
568
- * preserving the prompt-cache prefix. The agent rediscovers explicitly via
569
- * mcp({ connect: "server" }). Default: false. */
581
+ * and explicit reconnects won't rebuild the system prompt, preserving the
582
+ * prompt-cache prefix. Proxy/search/cache metadata still refreshes. Default: false. */
570
583
  freezeDirectTools?: boolean;
571
584
  autoAuth?: boolean;
572
585
  sampling?: boolean;
@@ -710,6 +723,8 @@ export interface McpPanelCallbacks {
710
723
 
711
724
  export interface McpPanelResult {
712
725
  changes: Map<string, true | string[] | false>;
726
+ /** Servers whose disabled flag changed during the panel session (name → new disabled state). */
727
+ disabledChanges: Map<string, boolean>;
713
728
  cancelled: boolean;
714
729
  }
715
730
 
@@ -1,6 +1,11 @@
1
1
  import { RESOURCE_MIME_TYPE } from "./ui-app-bridge-helpers.ts";
2
2
  import { UrlElicitationRequiredError, type ReadResourceResult } from "@modelcontextprotocol/client";
3
- import { ResourceFetchError, ResourceParseError } from "./errors.ts";
3
+ import {
4
+ getInputRequiredNeedsUiDetails,
5
+ InputRequiredNeedsUiError,
6
+ ResourceFetchError,
7
+ ResourceParseError,
8
+ } from "./errors.ts";
4
9
  import { logger } from "./logger.ts";
5
10
  import { SessionRecoveryAuthRequiredError, withSessionRecovery, type SessionRecoveryDeps } from "./session-recovery.ts";
6
11
  import type { McpServerManager } from "./server-manager.ts";
@@ -52,7 +57,14 @@ export class UiResourceHandler {
52
57
  ...(options.onNeedsAuth ? { onNeedsAuth: options.onNeedsAuth } : {}),
53
58
  },
54
59
  serverName,
55
- (connection) => connection.client.readResource({ uri }, this.manager.getRequestOptions(serverName, options.signal)),
60
+ async (connection) => {
61
+ const refreshRead = await this.manager.prepareResourceUse?.(serverName, uri, connection);
62
+ const requestOptions = this.manager.getRequestOptions(serverName, options.signal);
63
+ return connection.client.readResource(
64
+ { uri },
65
+ refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions,
66
+ );
67
+ },
56
68
  );
57
69
  } finally {
58
70
  this.manager.decrementInFlight(serverName);
@@ -63,6 +75,10 @@ export class UiResourceHandler {
63
75
  }
64
76
  } catch (error) {
65
77
  if (error instanceof UrlElicitationRequiredError || error instanceof SessionRecoveryAuthRequiredError) throw error;
78
+ const inputRequired = getInputRequiredNeedsUiDetails(error, { server: serverName, resourceUri: uri });
79
+ if (inputRequired) {
80
+ throw new InputRequiredNeedsUiError(inputRequired, error instanceof Error ? error : undefined);
81
+ }
66
82
  const message = error instanceof Error ? error.message : String(error);
67
83
  log.error("Failed to read resource", error instanceof Error ? error : undefined);
68
84
  throw new ResourceFetchError(uri, message, {