@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

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 (385) hide show
  1. package/README.md +8 -6
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -9,6 +9,7 @@ import { isExaAvailable, searchWithExa } from "./exa.ts";
9
9
  import { isBraveAvailable, searchWithBrave } from "./brave.ts";
10
10
  import { isOpenAISearchAvailable, searchWithOpenAI } from "./openai-search.ts";
11
11
  import { isParallelAvailable, searchWithParallel } from "./parallel.ts";
12
+ import { isParallelMcpAvailable, searchWithParallelMcp } from "./parallel-mcp.ts";
12
13
  import { isTinyFishAvailable, searchWithTinyFish } from "./tinyfish.ts";
13
14
  import { isSearch1APIAvailable, searchWithSearch1API } from "./search1api.ts";
14
15
  import { isSearchinfinityAvailable, searchWithSearchinfinity } from "./searchinfinity.ts";
@@ -26,9 +27,11 @@ import { isAnySearchAvailable, searchWithAnySearch } from "./anysearch.ts";
26
27
  import { isXaiSearchAvailable, searchWithXai } from "./xai-search.ts";
27
28
  import { isBrightDataAvailable, searchWithBrightData } from "./brightdata.ts";
28
29
  import { isSerpBaseAvailable, searchWithSerpBase } from "./serpbase.ts";
30
+ import { isSerperAvailable, searchWithSerper } from "./serper.ts";
31
+ import { isValyuAvailable, searchWithValyu } from "./valyu.ts";
29
32
  import { getWebSearchConfigPath } from "./utils.ts";
30
33
 
31
- export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "brightdata", "serpbase", "bocha"] as const;
34
+ export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "brightdata", "serpbase", "serper", "valyu", "bocha"] as const;
32
35
  export const SEARCH_PROVIDERS = ["auto", "all", ...RESOLVED_SEARCH_PROVIDERS] as const;
33
36
 
34
37
  export type ResolvedSearchProvider = typeof RESOLVED_SEARCH_PROVIDERS[number];
@@ -90,7 +93,7 @@ export interface AttributedSearchResponse extends SearchResponse {
90
93
 
91
94
  const CONFIG_PATH = getWebSearchConfigPath();
92
95
  const DEFAULT_SEARCH_MODEL = "gemini-3.6-flash";
93
- // Explicit-only providers (DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase) are deliberately absent:
96
+ // Explicit-only providers (Parallel MCP, DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase, Serper, Valyu) are deliberately absent:
94
97
  // `all` must never fan out to an opt-in or paid provider without the user asking for it.
95
98
  const ALL_SEARCH_PROVIDERS: ResolvedSearchProvider[] = ["searxng", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "ollama", "perplexity", "gemini", "bocha"];
96
99
  const VALID_ROUTING_KINDS = ["transient", "quota", "network", "invalid-response"] as const;
@@ -300,6 +303,7 @@ async function searchWithResolvedProvider(
300
303
  }
301
304
  if (provider === "brave") return { ...(await searchWithBrave(query, options)), provider };
302
305
  if (provider === "parallel") return { ...(await searchWithParallel(query, options)), provider };
306
+ if (provider === "parallel-mcp") return { ...(await searchWithParallelMcp(query, options)), provider };
303
307
  if (provider === "tinyfish") return { ...(await searchWithTinyFish(query, options)), provider };
304
308
  if (provider === "search1api") return { ...(await searchWithSearch1API(query, options)), provider };
305
309
  if (provider === "searchinfinity") return { ...(await searchWithSearchinfinity(query, options)), provider };
@@ -315,6 +319,8 @@ async function searchWithResolvedProvider(
315
319
  if (provider === "xai") return { ...(await searchWithXai(query, options, options.extensionContext)), provider };
316
320
  if (provider === "brightdata") return { ...(await searchWithBrightData(query, options)), provider };
317
321
  if (provider === "serpbase") return { ...(await searchWithSerpBase(query, options)), provider };
322
+ if (provider === "serper") return { ...(await searchWithSerper(query, options)), provider };
323
+ if (provider === "valyu") return { ...(await searchWithValyu(query, options)), provider };
318
324
  if (provider === "perplexity") return { ...(await searchWithPerplexity(query, options)), provider };
319
325
  if (provider === "searxng") return { ...(await searchWithSearXNG(query, options)), provider };
320
326
  if (provider === "duckduckgo") return { ...(await searchWithDuckDuckGo(query, options)), provider };
@@ -337,6 +343,7 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
337
343
  if (provider === "openai") return isOpenAISearchAvailable(options.extensionContext);
338
344
  if (provider === "brave") return isBraveAvailable();
339
345
  if (provider === "parallel") return isParallelAvailable();
346
+ if (provider === "parallel-mcp") return isParallelMcpAvailable();
340
347
  if (provider === "tinyfish") return isTinyFishAvailable();
341
348
  if (provider === "search1api") return isSearch1APIAvailable();
342
349
  if (provider === "searchinfinity") return isSearchinfinityAvailable();
@@ -352,6 +359,8 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
352
359
  if (provider === "xai") return isXaiSearchAvailable(options.extensionContext);
353
360
  if (provider === "brightdata") return isBrightDataAvailable();
354
361
  if (provider === "serpbase") return isSerpBaseAvailable();
362
+ if (provider === "serper") return isSerperAvailable();
363
+ if (provider === "valyu") return isValyuAvailable();
355
364
  if (provider === "perplexity") return isPerplexityAvailable();
356
365
  if (provider === "searxng") return isSearXNGAvailable();
357
366
  if (provider === "duckduckgo") return isDuckDuckGoAvailable();
@@ -361,6 +370,7 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
361
370
 
362
371
  function providerLabel(provider: ResolvedSearchProvider): string {
363
372
  if (provider === "openai") return "OpenAI";
373
+ if (provider === "parallel-mcp") return "Parallel MCP";
364
374
  if (provider === "tinyfish") return "TinyFish";
365
375
  if (provider === "search1api") return "Search1API";
366
376
  if (provider === "searchinfinity") return "Searchinfinity";
@@ -375,6 +385,8 @@ function providerLabel(provider: ResolvedSearchProvider): string {
375
385
  if (provider === "xai") return "xAI";
376
386
  if (provider === "brightdata") return "Bright Data";
377
387
  if (provider === "serpbase") return "SerpBase";
388
+ if (provider === "serper") return "Serper";
389
+ if (provider === "valyu") return "Valyu";
378
390
  return provider.charAt(0).toUpperCase() + provider.slice(1);
379
391
  }
380
392
 
@@ -401,7 +413,7 @@ async function searchWithProviders(
401
413
  : await isResolvedProviderAvailable(provider, options),
402
414
  })))).filter((entry) => entry.available).map((entry) => entry.provider);
403
415
  if (providers.length === 0) {
404
- throw new Error("No configured search provider available for provider \"all\". AnySearch, xAI, Bright Data, and SerpBase are excluded.");
416
+ throw new Error("No configured search provider available for provider \"all\". Parallel MCP, AnySearch, xAI, Bright Data, SerpBase, Serper, and Valyu are excluded.");
405
417
  }
406
418
 
407
419
  const settled = await Promise.allSettled(
@@ -693,7 +705,7 @@ export async function search(query: string, options: FullSearchOptions = {}): Pr
693
705
  " 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TINYFISH_API_KEY, SEARCH1API_KEY, SEARCHINFINITY_API_KEY, QUERIT_API_KEY, TAVILY_API_KEY, FIRECRAWL_BASE_URL, JINA_API_KEY, SERPDIVE_API_KEY, KAGI_API_KEY, BOCHA_API_KEY, OLLAMA_API_KEY, SEARXNG_BASE_URL, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\n" +
694
706
  " 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\n" +
695
707
  " 5. Sign into gemini.google.com in a supported Chromium-based browser\n" +
696
- " 6. Explicitly select provider: \"anysearch\" for anonymous AnySearch, \"xai\" for Grok, \"brightdata\" with brightdataSerpZone for paid Bright Data SERP, or \"serpbase\" with serpbaseApiKey for paid Google SERP"
708
+ " 6. Explicitly select provider: \"anysearch\" for anonymous AnySearch, \"xai\" for Grok, \"brightdata\" with brightdataSerpZone for paid Bright Data SERP, \"serpbase\" or \"serper\" for Google SERP, or \"valyu\" for research search"
697
709
  );
698
710
  }
699
711
 
@@ -1,6 +1,7 @@
1
- import { existsSync, readFileSync, rmSync, statSync, readdirSync, openSync, readSync, closeSync, realpathSync } from "node:fs";
1
+ import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, rmSync, statSync, unlinkSync } from "node:fs";
2
2
  import { execFile, spawn, type ChildProcess } from "node:child_process";
3
- import { extname, join, resolve as resolvePath, sep as pathSep } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { basename, dirname, extname, join, resolve as resolvePath, sep as pathSep } from "node:path";
4
5
  import { activityMonitor } from "./activity.ts";
5
6
  import type { ExtractedContent } from "./extract.ts";
6
7
  import { checkGhAvailable, checkRepoSize, fetchViaApi, showGhHint } from "./github-api.ts";
@@ -39,10 +40,15 @@ export interface GitHubUrlInfo {
39
40
  }
40
41
 
41
42
  interface CachedClone {
42
- localPath: string;
43
+ destination: CloneDestination;
43
44
  clonePromise: Promise<string | null>;
44
45
  }
45
46
 
47
+ interface CloneDestination {
48
+ rootPath: string;
49
+ localPath: string;
50
+ }
51
+
46
52
  interface GitHubCloneConfig {
47
53
  enabled: boolean;
48
54
  maxRepoSizeMB: number;
@@ -137,20 +143,21 @@ export function parseGitHubUrl(url: string): GitHubUrlInfo | null {
137
143
  const host = parsed.hostname.toLowerCase();
138
144
  if (host !== "github.com" && host !== "www.github.com") return null;
139
145
 
140
- const segments = parsed.pathname
141
- .split("/")
142
- .filter(Boolean)
143
- .map((segment) => {
144
- try {
145
- return decodeURIComponent(segment);
146
- } catch {
147
- return segment;
148
- }
149
- });
146
+ const segments: string[] = [];
147
+ for (const segment of parsed.pathname.split("/").filter(Boolean)) {
148
+ try {
149
+ segments.push(decodeURIComponent(segment));
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
150
154
  if (segments.length < 2) return null;
151
155
 
152
156
  const owner = segments[0];
153
157
  const repo = segments[1].replace(/\.git$/, "");
158
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(owner)) return null;
159
+ if (owner.includes("--")) return null;
160
+ if (!/^[A-Za-z0-9._-]{1,100}$/.test(repo) || repo === "." || repo === "..") return null;
154
161
 
155
162
  if (NON_CODE_SEGMENTS.has(segments[2]?.toLowerCase())) return null;
156
163
 
@@ -163,6 +170,7 @@ export function parseGitHubUrl(url: string): GitHubUrlInfo | null {
163
170
  if (segments.length < 4) return null;
164
171
 
165
172
  const ref = segments[3];
173
+ if (ref.length === 0 || ref.length > 1024 || /[\0-\x1f\x7f]/.test(ref)) return null;
166
174
  const refIsFullSha = /^[0-9a-f]{40}$/.test(ref);
167
175
  const pathParts = segments.slice(4);
168
176
  const path = pathParts.length > 0 ? pathParts.join("/") : "";
@@ -181,9 +189,32 @@ function cacheKey(owner: string, repo: string, ref?: string): string {
181
189
  return ref ? `${owner}/${repo}@${ref}` : `${owner}/${repo}`;
182
190
  }
183
191
 
184
- function cloneDir(config: GitHubCloneConfig, owner: string, repo: string, ref?: string): string {
185
- const dirName = ref ? `${repo}@${ref}` : repo;
186
- return join(config.clonePath, owner, dirName);
192
+ function cloneDestination(config: GitHubCloneConfig, owner: string, repo: string, ref?: string): CloneDestination | null {
193
+ try {
194
+ mkdirSync(resolvePath(config.clonePath), { recursive: true });
195
+ const rootPath = realpathSync(resolvePath(config.clonePath));
196
+ const digest = createHash("sha256").update(JSON.stringify([owner, repo, ref ?? null])).digest("hex");
197
+ const localPath = resolvePath(rootPath, digest);
198
+ if (dirname(localPath) !== rootPath) return null;
199
+ return { rootPath, localPath };
200
+ } catch {
201
+ return null;
202
+ }
203
+ }
204
+
205
+ function removeCloneDestination(destination: CloneDestination): boolean {
206
+ const rootPath = resolvePath(destination.rootPath);
207
+ const localPath = resolvePath(destination.localPath);
208
+ if (dirname(localPath) !== rootPath || !/^[0-9a-f]{64}$/.test(basename(localPath))) return false;
209
+ try {
210
+ const entry = lstatSync(localPath);
211
+ if (entry.isSymbolicLink()) unlinkSync(localPath);
212
+ else rmSync(localPath, { recursive: true, force: true });
213
+ return true;
214
+ } catch (err) {
215
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return true;
216
+ return false;
217
+ }
187
218
  }
188
219
 
189
220
  const PROCESS_KILL_GRACE_MS = 3000;
@@ -225,8 +256,9 @@ function terminateProcessTree(child: ChildProcess): void {
225
256
  forceKill.unref();
226
257
  }
227
258
 
228
- function execClone(args: string[], localPath: string, timeoutMs: number, signal?: AbortSignal): Promise<string | null> {
259
+ function execClone(args: string[], destination: CloneDestination, timeoutMs: number, signal?: AbortSignal): Promise<string | null> {
229
260
  return new Promise((resolve) => {
261
+ const { localPath } = destination;
230
262
  let settled = false;
231
263
  let timeout: ReturnType<typeof setTimeout> | undefined;
232
264
  let onAbort: (() => void) | undefined;
@@ -238,10 +270,7 @@ function execClone(args: string[], localPath: string, timeoutMs: number, signal?
238
270
  if (signal && onAbort) signal.removeEventListener("abort", onAbort);
239
271
 
240
272
  if (!success) {
241
- try {
242
- rmSync(localPath, { recursive: true, force: true });
243
- } catch {
244
- }
273
+ removeCloneDestination(destination);
245
274
  resolve(null);
246
275
  return;
247
276
  }
@@ -282,14 +311,11 @@ async function cloneRepo(
282
311
  repo: string,
283
312
  ref: string | undefined,
284
313
  config: GitHubCloneConfig,
314
+ destination: CloneDestination,
285
315
  signal?: AbortSignal,
286
316
  ): Promise<string | null> {
287
- const localPath = cloneDir(config, owner, repo, ref);
288
-
289
- try {
290
- rmSync(localPath, { recursive: true, force: true });
291
- } catch {
292
- }
317
+ const { localPath } = destination;
318
+ if (!removeCloneDestination(destination)) return null;
293
319
 
294
320
  const timeoutMs = config.cloneTimeoutSeconds * 1000;
295
321
  const hasGh = await checkGhAvailable();
@@ -297,7 +323,7 @@ async function cloneRepo(
297
323
  if (hasGh) {
298
324
  const args = ["gh", "repo", "clone", `${owner}/${repo}`, localPath, "--", "--depth", "1", "--single-branch"];
299
325
  if (ref) args.push("--branch", ref);
300
- return execClone(args, localPath, timeoutMs, signal);
326
+ return execClone(args, destination, timeoutMs, signal);
301
327
  }
302
328
 
303
329
  showGhHint();
@@ -306,7 +332,7 @@ async function cloneRepo(
306
332
  const args = ["git", "clone", "--depth", "1", "--single-branch"];
307
333
  if (ref) args.push("--branch", ref);
308
334
  args.push(gitUrl, localPath);
309
- return execClone(args, localPath, timeoutMs, signal);
335
+ return execClone(args, destination, timeoutMs, signal);
310
336
  }
311
337
 
312
338
  function isBinaryFile(filePath: string): boolean {
@@ -671,9 +697,15 @@ export async function extractGitHub(
671
697
  return cachedResult;
672
698
  }
673
699
 
674
- const clonePromise = cloneRepo(owner, repo, info.ref, config, signal);
675
- const localPath = cloneDir(config, owner, repo, info.ref);
676
- cloneCache.set(key, { localPath, clonePromise });
700
+ const destination = cloneDestination(config, owner, repo, info.ref);
701
+ if (!destination) {
702
+ const apiFallback = await fetchViaApi(url, owner, repo, info);
703
+ if (apiFallback) activityMonitor.logComplete(activityId, 200);
704
+ else activityMonitor.logError(activityId, "invalid clone destination");
705
+ return apiFallback;
706
+ }
707
+ const clonePromise = cloneRepo(owner, repo, info.ref, config, destination, signal);
708
+ cloneCache.set(key, { destination, clonePromise });
677
709
 
678
710
  const result = await clonePromise;
679
711
  if (signal?.aborted) {
@@ -707,10 +739,7 @@ export async function extractGitHub(
707
739
 
708
740
  export function clearCloneCache(): void {
709
741
  for (const entry of cloneCache.values()) {
710
- try {
711
- rmSync(entry.localPath, { recursive: true, force: true });
712
- } catch {
713
- }
742
+ removeCloneDestination(entry.destination);
714
743
  }
715
744
  cloneCache.clear();
716
745
  cachedConfig = null;
@@ -1,12 +1,13 @@
1
1
  import type { AgentToolResult, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { Box, Text, truncateToWidth, type KeyId } from "@earendil-works/pi-tui";
3
3
  import { Type } from "typebox";
4
- import { StringEnum, complete, type Api, type ImageContent, type Model, type TextContent } from "@earendil-works/pi-ai/compat";
4
+ import { StringEnum, type ImageContent, type TextContent } from "@earendil-works/pi-ai/compat";
5
5
  import type { ExtractedContent, ExtractOptions } from "./extract.ts";
6
6
  import { normalizeFetchContentParams } from "./fetch-params.ts";
7
7
  import { resolveAuthFetchProfile, type AuthFetchProfile } from "./auth-fetch.ts";
8
8
  import { findContent, type FindMode } from "./content-find.ts";
9
9
  import { answerFromPage } from "./page-query.ts";
10
+ import { rewriteSearchQuery } from "./query-rewrite.ts";
10
11
  import { clearCloneCache } from "./github-extract.ts";
11
12
  import { getConfiguredSearchRouting, normalizeSearchProviderSelection, RESOLVED_SEARCH_PROVIDERS, SEARCH_PROVIDERS, search, type AttributedSearchResponse, type SearchProvider, type SearchProviderSelection, type ResolvedSearchProvider } from "./gemini-search.ts";
12
13
  import type { SearchResult } from "./perplexity.ts";
@@ -33,7 +34,7 @@ import {
33
34
  type SummaryMeta,
34
35
  } from "./summary-review.ts";
35
36
  import { randomUUID } from "node:crypto";
36
- import { execFileSync } from "node:child_process";
37
+ import { execFileSync, spawn } from "node:child_process";
37
38
  import { createRequire } from "node:module";
38
39
  import { platform } from "node:os";
39
40
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
@@ -46,6 +47,7 @@ import { isBrowserCookieAccessAllowed } from "./gemini-web-config.ts";
46
47
  import { isBraveAvailable } from "./brave.ts";
47
48
  import { isOpenAISearchAvailable } from "./openai-search.ts";
48
49
  import { isParallelAvailable } from "./parallel.ts";
50
+ import { isParallelMcpAvailable } from "./parallel-mcp.ts";
49
51
  import { isTinyFishAvailable } from "./tinyfish.ts";
50
52
  import { isSearch1APIAvailable } from "./search1api.ts";
51
53
  import { isSearchinfinityAvailable } from "./searchinfinity.ts";
@@ -63,8 +65,10 @@ import { isAnySearchAvailable } from "./anysearch.ts";
63
65
  import { isXaiSearchAvailable } from "./xai-search.ts";
64
66
  import { isBrightDataAvailable } from "./brightdata.ts";
65
67
  import { isSerpBaseAvailable } from "./serpbase.ts";
68
+ import { isSerperAvailable } from "./serper.ts";
69
+ import { isValyuAvailable } from "./valyu.ts";
66
70
  import { buildSearchErrorPlan, type SearchErrorDetails, type SearchErrorPlan } from "./render-search-error.ts";
67
- import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
71
+ import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix } from "./summary-model-scope.ts";
68
72
  import {
69
73
  buildResearchArtifact,
70
74
  withClaimAssessment,
@@ -117,7 +121,9 @@ interface WebSearchConfig {
117
121
  kagiApiKey?: unknown;
118
122
  ollamaApiKey?: unknown;
119
123
  serpbaseApiKey?: unknown;
124
+ serperApiKey?: unknown;
120
125
  tinyfishApiKey?: unknown;
126
+ valyuApiKey?: unknown;
121
127
  xaiApiKey?: unknown;
122
128
  provider?: unknown;
123
129
  searchProvider?: unknown;
@@ -151,6 +157,7 @@ interface ProviderAvailability {
151
157
  openai: boolean;
152
158
  brave: boolean;
153
159
  parallel: boolean;
160
+ "parallel-mcp": boolean;
154
161
  tinyfish: boolean;
155
162
  search1api: boolean;
156
163
  searchinfinity: boolean;
@@ -171,6 +178,8 @@ interface ProviderAvailability {
171
178
  xai: boolean;
172
179
  brightdata: boolean;
173
180
  serpbase: boolean;
181
+ serper: boolean;
182
+ valyu: boolean;
174
183
  }
175
184
 
176
185
  type WebSearchWorkflow = "none" | "summary-review" | "auto-summary";
@@ -222,8 +231,6 @@ type ToolNames = {
222
231
  getSearchContent: string;
223
232
  };
224
233
 
225
- type ProviderHeaders = Record<string, string | null>;
226
-
227
234
  const DEFAULT_TOOL_NAMES: ToolNames = {
228
235
  webSearch: "web_search",
229
236
  sourceCheck: "source_check",
@@ -382,6 +389,7 @@ async function getProviderAvailability(ctx: ExtensionContext): Promise<ProviderA
382
389
  openai: await isOpenAISearchAvailable(ctx),
383
390
  brave: isBraveAvailable(),
384
391
  parallel: isParallelAvailable(),
392
+ "parallel-mcp": isParallelMcpAvailable(),
385
393
  tinyfish: isTinyFishAvailable(),
386
394
  search1api: isSearch1APIAvailable(),
387
395
  searchinfinity: isSearchinfinityAvailable(),
@@ -402,10 +410,12 @@ async function getProviderAvailability(ctx: ExtensionContext): Promise<ProviderA
402
410
  xai: await isXaiSearchAvailable(ctx),
403
411
  brightdata: isBrightDataAvailable(),
404
412
  serpbase: isSerpBaseAvailable(),
413
+ serper: isSerperAvailable(),
414
+ valyu: isValyuAvailable(),
405
415
  };
406
416
  return {
407
- // DuckDuckGo, AnySearch, xAI, Bright Data, and SerpBase are explicit-only, so they never make `all` eligible.
408
- all: Object.entries(providers).some(([provider, available]) => provider !== "duckduckgo" && provider !== "anysearch" && provider !== "xai" && provider !== "brightdata" && provider !== "serpbase" && provider !== "gemini" && available) || geminiApiAvail,
417
+ // Parallel MCP, DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase, Serper, and Valyu are explicit-only, so they never make `all` eligible.
418
+ all: Object.entries(providers).some(([provider, available]) => provider !== "parallel-mcp" && provider !== "duckduckgo" && provider !== "anysearch" && provider !== "xai" && provider !== "brightdata" && provider !== "serpbase" && provider !== "serper" && provider !== "valyu" && provider !== "gemini" && available) || geminiApiAvail,
409
419
  ...providers,
410
420
  };
411
421
  }
@@ -734,11 +744,26 @@ function closeCurator(callId?: string): void {
734
744
 
735
745
  async function openInBrowser(pi: ExtensionAPI, url: string): Promise<void> {
736
746
  const plat = platform();
747
+ if (plat !== "darwin" && plat !== "win32") {
748
+ await new Promise<void>((resolve, reject) => {
749
+ const child = spawn("xdg-open", [url], { detached: true, stdio: "ignore" });
750
+ const timer = setTimeout(resolve, 100);
751
+ child.once("error", (err) => {
752
+ clearTimeout(timer);
753
+ reject(err);
754
+ });
755
+ child.once("exit", (code) => {
756
+ clearTimeout(timer);
757
+ if (code === 0) resolve();
758
+ else reject(new Error(`Failed to open browser (exit code ${code ?? "unknown"})`));
759
+ });
760
+ child.unref();
761
+ });
762
+ return;
763
+ }
737
764
  const result = plat === "darwin"
738
765
  ? await pi.exec("open", [url])
739
- : plat === "win32"
740
- ? await pi.exec("cmd", ["/c", "start", "", url])
741
- : await pi.exec("xdg-open", [url]);
766
+ : await pi.exec("cmd", ["/c", "start", "", url]);
742
767
  if (result.code !== 0) {
743
768
  throw new Error(result.stderr || `Failed to open browser (exit code ${result.code})`);
744
769
  }
@@ -974,10 +999,15 @@ export default function (pi: ExtensionAPI) {
974
999
  } satisfies StoredSearchData & { type: "fetch"; urls: ExtractedContent[] };
975
1000
  pi.appendEntry("web-search-results", storeFetchedContentResult(fetchId, data));
976
1001
  const ok = fetched.filter(f => !f.error).length;
1002
+ const availability = ok === fetched.length
1003
+ ? "Full page content now available."
1004
+ : ok > 0
1005
+ ? "Partial page content now available."
1006
+ : "No page content was fetched. Stored fetch diagnostics are available.";
977
1007
  pi.sendMessage(
978
1008
  {
979
1009
  customType: "web-search-content-ready",
980
- content: `Content fetched for ${ok}/${fetched.length} URLs [${fetchId}]. Full page content now available.`,
1010
+ content: `Content fetched for ${ok}/${fetched.length} URLs [${fetchId}]. ${availability}`,
981
1011
  display: true,
982
1012
  },
983
1013
  { triggerTurn: true },
@@ -1088,47 +1118,6 @@ export default function (pi: ExtensionAPI) {
1088
1118
  };
1089
1119
  }
1090
1120
 
1091
- async function resolveFirstAvailableModel(
1092
- ctx: SummaryGenerationContext,
1093
- candidates: Array<{ provider: string; id: string }>,
1094
- ): Promise<{ model: Model<Api>; apiKey: string; headers?: ProviderHeaders }> {
1095
- const enabledModelPatterns = loadEnabledModelPatterns(ctx);
1096
- for (const { provider, id } of candidates) {
1097
- const model = findModelWithProviderRouting(ctx.modelRegistry, provider, id);
1098
- if (!model || !modelMatchesEnabledPatterns(model, enabledModelPatterns)) continue;
1099
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
1100
- if (auth.ok && auth.apiKey) return { model, apiKey: auth.apiKey, headers: auth.headers };
1101
- }
1102
- throw new Error(`No enabled model available: ${candidates.map(c => `${c.provider}/${c.id}`).join(", ")}`);
1103
- }
1104
-
1105
- async function rewriteSearchQuery(query: string, ctx: SummaryGenerationContext, signal: AbortSignal): Promise<string> {
1106
- const { model, apiKey, headers } = await resolveFirstAvailableModel(ctx, [
1107
- { provider: "anthropic", id: "claude-haiku-4-5" },
1108
- { provider: "google", id: "gemini-3.6-flash" },
1109
- { provider: "openai", id: "gpt-4.1-mini" },
1110
- ]);
1111
- const response = await complete(
1112
- model,
1113
- {
1114
- messages: [{
1115
- role: "user",
1116
- content: [{ type: "text", text: `Rewrite this web search query to get better, more specific results. Add relevant year qualifiers, precise technical terms, and specificity. Return ONLY the improved query text, nothing else.\n\nQuery: ${query}` }],
1117
- timestamp: Date.now(),
1118
- }],
1119
- },
1120
- { apiKey, headers, signal },
1121
- );
1122
- if (response.stopReason === "aborted") throw new Error("Aborted");
1123
- const contentParts = Array.isArray(response.content) ? response.content : [];
1124
- const text = contentParts
1125
- .map(part => part.type === "text" ? part.text : "")
1126
- .join("")
1127
- .trim();
1128
- if (!text) throw new Error("Rewrite returned empty response");
1129
- return text;
1130
- }
1131
-
1132
1121
  async function generateSummaryForSelectedIndices(
1133
1122
  selectedQueryIndices: number[],
1134
1123
  resultsByIndex: Map<number, QueryResultData>,
@@ -1211,20 +1200,30 @@ export default function (pi: ExtensionAPI) {
1211
1200
  const configuredSummaryModel = typeof config.summaryModel === "string" ? config.summaryModel.trim() : "";
1212
1201
  const preferredDefaults = [
1213
1202
  { provider: "anthropic", id: "claude-haiku-4-5" },
1214
- { provider: "openai-codex", id: "gpt-5.3-codex-spark" },
1203
+ { provider: "openai-codex", id: "gpt-5.6-luna" },
1204
+ { provider: "openai-codex", id: "gpt-5.6-terra" },
1205
+ { provider: "google", id: "gemini-3.6-flash" },
1206
+ { provider: "openai", id: "gpt-5-mini" },
1207
+ { provider: "deepseek", id: "deepseek-v4-flash" },
1215
1208
  ];
1216
1209
 
1217
1210
  const resolveAvailableModelValue = (selector: string): string | null => {
1218
- const slashIndex = selector.indexOf("/");
1219
- if (slashIndex <= 0 || slashIndex >= selector.length - 1) return null;
1211
+ const parsed = splitThinkingSuffix(selector);
1212
+ const slashIndex = parsed.value.indexOf("/");
1213
+ if (slashIndex <= 0 || slashIndex >= parsed.value.length - 1) return null;
1220
1214
  const model = findModelWithProviderRouting(
1221
1215
  summaryContext.modelRegistry,
1222
- selector.slice(0, slashIndex),
1223
- selector.slice(slashIndex + 1),
1216
+ parsed.value.slice(0, slashIndex),
1217
+ parsed.value.slice(slashIndex + 1),
1224
1218
  );
1225
1219
  if (!model) return null;
1226
1220
  const value = `${model.provider}/${model.id}`;
1227
- return availableValues.has(value) ? value : null;
1221
+ if (!availableValues.has(value)) return null;
1222
+ if (selector !== value && !seen.has(selector)) {
1223
+ seen.add(selector);
1224
+ summaryModels.push({ value: selector, label: selector });
1225
+ }
1226
+ return selector;
1228
1227
  };
1229
1228
 
1230
1229
  let defaultSummaryModel: string | null = null;
@@ -1661,7 +1660,7 @@ export default function (pi: ExtensionAPI) {
1661
1660
  name: toolNames.webSearch,
1662
1661
  label: "Web Search",
1663
1662
  description:
1664
- `Search the web using OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, SearXNG, DuckDuckGo, Exa, Perplexity, Gemini, AnySearch, xAI, Bright Data, or SerpBase. Pass a provider array to search only those providers simultaneously, or use provider "all" to search every eligible provider except DuckDuckGo, AnySearch, xAI, Bright Data, and SerpBase. Returns an AI-synthesized answer with source citations. OpenAI search uses a Codex subscription or OpenAI API key; xAI search uses a SuperGrok/X Premium subscription or xAI API key. DuckDuckGo, AnySearch, xAI, Bright Data, and SerpBase are available only when explicitly selected. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches auto-open the interactive browser curator and stream results live; set workflow to "none" to skip curation or "auto-summary" for a model-generated summary without the browser curator. The configured provider is used when provider is omitted or set to auto; omit provider unless explicitly overriding it. Without a configured provider, auto-selects OpenAI when suitable and available, then Exa, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Perplexity, Gemini API, or Gemini Web. When SearXNG is configured, it is preferred first for local/private search.`,
1663
+ `Search the web using OpenAI, Brave, Parallel, Parallel MCP, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, SearXNG, DuckDuckGo, Exa, Perplexity, Gemini, AnySearch, Valyu, xAI, Bright Data, SerpBase, or Serper. Pass a provider array to search only those providers simultaneously, or use provider "all" to search every eligible provider except Parallel MCP, DuckDuckGo, AnySearch, Valyu, xAI, Bright Data, SerpBase, and Serper. Returns an AI-synthesized answer with source citations. OpenAI search uses a Codex subscription or OpenAI API key; xAI search uses a SuperGrok/X Premium subscription or xAI API key. Parallel MCP, DuckDuckGo, AnySearch, Valyu, xAI, Bright Data, SerpBase, and Serper are available only when explicitly selected. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches auto-open the interactive browser curator and stream results live; set workflow to "none" to skip curation or "auto-summary" for a model-generated summary without the browser curator. The configured provider is used when provider is omitted or set to auto; omit provider unless explicitly overriding it. Without a configured provider, auto-selects OpenAI when suitable and available, then Exa, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Perplexity, Gemini API, or Gemini Web. When SearXNG is configured, it is preferred first for local/private search.`,
1665
1664
  promptSnippet:
1666
1665
  "Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage. Omit provider unless explicitly overriding the configured default.",
1667
1666
  parameters: Type.Object({
@@ -1673,7 +1672,7 @@ export default function (pi: ExtensionAPI) {
1673
1672
  StringEnum(["day", "week", "month", "year"], { description: "Filter by recency" }),
1674
1673
  ),
1675
1674
  domainFilter: Type.Optional(Type.Array(Type.String(), { description: "Limit to domains (prefix with - to exclude)" })),
1676
- provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; use all to search every eligible provider except DuckDuckGo, AnySearch, xAI, Bright Data, and SerpBase, omit this field to use the configured provider, or use auto when none is configured")),
1675
+ provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; use all to search every eligible provider except Parallel MCP, DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase, Serper, and Valyu, omit this field to use the configured provider, or use auto when none is configured")),
1677
1676
  workflow: Type.Optional(
1678
1677
  StringEnum(["none", "summary-review", "auto-summary"], {
1679
1678
  description: "Search workflow mode: none = no curator, summary-review = open curator with auto summary draft (default), auto-summary = generate summary without opening curator",
@@ -2241,7 +2240,7 @@ export default function (pi: ExtensionAPI) {
2241
2240
  fetchContent: Type.Optional(Type.Boolean({ description: "Fetch up to 5 result pages for exact passage extraction." })),
2242
2241
  recencyFilter: Type.Optional(StringEnum(["day", "week", "month", "year"], { description: "Filter by recency." })),
2243
2242
  domainFilter: Type.Optional(Type.Array(Type.String(), { description: "Limit to domains; prefix with - to exclude." })),
2244
- provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; all searches every eligible provider except DuckDuckGo, AnySearch, xAI, Bright Data, and SerpBase")),
2243
+ provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; all searches every eligible provider except DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase, Serper, and Valyu")),
2245
2244
  }),
2246
2245
  async execute(_callId, params, signal, _onUpdate, ctx) {
2247
2246
  const claim = typeof params.claim === "string" ? params.claim.trim() : "";
@@ -19,7 +19,7 @@ const MODEL_PREFERENCE = [
19
19
  (id: string) => id.includes("terra"),
20
20
  (id: string) => /^gpt-\d+(\.\d+)?$/.test(id),
21
21
  ];
22
- const SEARCH_PROVIDERS = ["openai-codex", "openai"] as const;
22
+ const DEFAULT_SEARCH_PROVIDERS: readonly string[] = ["openai-codex", "openai"];
23
23
 
24
24
  function pickSearchModel<T extends { id: string }>(models: readonly T[]): T | undefined {
25
25
  const candidates = models
@@ -36,12 +36,13 @@ interface WebSearchConfig {
36
36
  openaiApiKey?: unknown;
37
37
  openaiResponsesUrl?: unknown;
38
38
  openaiSearchModel?: unknown;
39
+ openaiSearchProviders?: unknown;
39
40
  }
40
41
 
41
42
  type ProviderHeaders = Record<string, string | null>;
42
43
 
43
44
  interface OpenAIAuth {
44
- provider: "openai-codex" | "openai";
45
+ provider: string;
45
46
  apiKey: string;
46
47
  model: string;
47
48
  headers: ProviderHeaders;
@@ -149,6 +150,14 @@ function resolveConfiguredResponsesUrl(value: unknown): string {
149
150
  return url.toString();
150
151
  }
151
152
 
153
+ function resolveConfiguredSearchProviders(value: unknown): readonly string[] {
154
+ if (value === undefined) return DEFAULT_SEARCH_PROVIDERS;
155
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim().length === 0)) {
156
+ throw new Error(`openaiSearchProviders in ${CONFIG_PATH} must be an array of non-empty Pi provider ids`);
157
+ }
158
+ return value.map((entry) => entry.trim());
159
+ }
160
+
152
161
  function resolveConfiguredSearchModel(value: unknown): string | undefined {
153
162
  if (value == null) return undefined;
154
163
  if (typeof value !== "string" || value.trim().length === 0) {
@@ -165,14 +174,14 @@ function toRequestHeaders(headers: ProviderHeaders): Record<string, string> {
165
174
  return requestHeaders;
166
175
  }
167
176
 
168
- async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, modelOverride?: string): Promise<OpenAIAuth | undefined> {
177
+ async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, providers: readonly string[], modelOverride?: string): Promise<OpenAIAuth | undefined> {
169
178
  let models: ReturnType<typeof ctx.modelRegistry.getAll>;
170
179
  try {
171
180
  models = ctx.modelRegistry.getAll();
172
181
  } catch {
173
182
  return undefined;
174
183
  }
175
- for (const provider of SEARCH_PROVIDERS) {
184
+ for (const provider of providers) {
176
185
  const preferred = pickSearchModel(models.filter((model) => model.provider === provider));
177
186
  if (!preferred) continue;
178
187
  try {
@@ -196,8 +205,9 @@ export async function resolveOpenAIAuth(ctx?: ExtensionContext, signal?: AbortSi
196
205
  const config = loadConfig();
197
206
  const responsesUrl = resolveConfiguredResponsesUrl(config.openaiResponsesUrl);
198
207
  const modelOverride = resolveConfiguredSearchModel(config.openaiSearchModel);
208
+ const providers = resolveConfiguredSearchProviders(config.openaiSearchProviders);
199
209
  if (ctx) {
200
- const auth = await resolvePiAuth(ctx, responsesUrl, modelOverride);
210
+ const auth = await resolvePiAuth(ctx, responsesUrl, providers, modelOverride);
201
211
  if (auth) return auth;
202
212
  }
203
213
 
@@ -221,7 +231,8 @@ export async function resolveOpenAIAuth(ctx?: ExtensionContext, signal?: AbortSi
221
231
  export async function isOpenAISearchAvailable(ctx?: ExtensionContext): Promise<boolean> {
222
232
  const config = loadConfig();
223
233
  const responsesUrl = resolveConfiguredResponsesUrl(config.openaiResponsesUrl);
224
- if (ctx && await resolvePiAuth(ctx, responsesUrl)) return true;
234
+ const providers = resolveConfiguredSearchProviders(config.openaiSearchProviders);
235
+ if (ctx && await resolvePiAuth(ctx, responsesUrl, providers)) return true;
225
236
  return hasCredentialSource({
226
237
  provider: "OpenAI",
227
238
  configuredValue: config.openaiApiKey,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-access",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video understanding, and local video analysis for Pi coding agent. Supports OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Ollama, AnySearch, Bright Data SERP, SerpBase, SearXNG, Exa, Perplexity, and Gemini.",
5
5
  "type": "module",
6
6
  "scripts": {