@herbertgao/pi-extensions 2026.9.0 → 2026.9.2

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 (360) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
  12. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  13. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  14. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  15. package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  17. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  20. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  22. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  23. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  24. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  25. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  26. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  27. package/node_modules/pi-lens/CHANGELOG.md +1072 -0
  28. package/node_modules/pi-lens/README.md +3 -0
  29. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  30. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  31. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
  32. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  33. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  34. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  35. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  36. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  37. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  39. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
  40. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  41. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
  42. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  43. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  44. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  45. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
  46. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  47. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
  51. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  52. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
  53. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  54. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  56. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  57. package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
  58. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  59. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  60. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  61. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
  62. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  63. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  64. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  65. package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
  66. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  67. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  68. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  69. package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
  70. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  71. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  72. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
  73. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  74. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  75. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  76. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  77. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  78. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  79. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  80. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  81. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
  82. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  83. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
  84. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
  86. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  87. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  90. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  146. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
  148. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  149. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  150. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  151. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  153. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  154. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  155. package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  158. package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
  159. package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
  160. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  161. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
  162. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  163. package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
  164. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  165. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  166. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  167. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  168. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  169. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  170. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  171. package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
  172. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  173. package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
  174. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  175. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
  176. package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
  177. package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
  178. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
  179. package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
  180. package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
  181. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  182. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
  183. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
  185. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  186. package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
  187. package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
  188. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  189. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
  191. package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
  192. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
  194. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
  197. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  198. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  199. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  200. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  201. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
  202. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  203. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  204. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  205. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  206. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  207. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  208. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  209. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  210. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  211. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  212. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
  213. package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
  214. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  215. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  216. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  217. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
  218. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  219. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  220. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
  221. package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
  222. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  223. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  224. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  225. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  226. package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
  227. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  228. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  229. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  230. package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
  231. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  232. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  233. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  234. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  235. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  236. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  237. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  238. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  239. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  240. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  241. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
  242. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  243. package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
  244. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  245. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  246. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  247. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  248. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  249. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  250. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  251. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
  252. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  253. package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
  254. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  255. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  256. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
  257. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  258. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  259. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  260. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  261. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  262. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
  263. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  264. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  265. package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
  266. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
  267. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  268. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  269. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  270. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  271. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
  272. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  273. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  274. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  275. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  277. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  278. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  279. package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
  280. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  281. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  282. package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
  283. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  284. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  285. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  286. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  287. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
  288. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  289. package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
  290. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  291. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
  292. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  293. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  294. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  295. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  296. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  297. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  298. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
  299. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  300. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
  301. package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
  302. package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
  303. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  304. package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
  305. package/node_modules/pi-lens/dist/index.js +65899 -55340
  306. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  307. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  308. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  309. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  310. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  311. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  312. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
  313. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
  314. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  315. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  316. package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
  317. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  318. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
  319. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  320. package/node_modules/pi-lens/docs/configuration.md +222 -0
  321. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  322. package/node_modules/pi-lens/docs/features.md +44 -5
  323. package/node_modules/pi-lens/docs/globalconfig.md +20 -0
  324. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  325. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  326. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  327. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  328. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
  329. package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
  330. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  331. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  332. package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
  333. package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
  334. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  335. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  336. package/node_modules/pi-lens/docs/word-index.md +1 -1
  337. package/node_modules/pi-lens/package.json +18 -35
  338. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  339. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  340. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  341. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  342. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  343. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  344. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  345. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  346. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  347. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  348. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  349. package/node_modules/pi-web-access/README.md +62 -15
  350. package/node_modules/pi-web-access/curator-page.ts +3 -1
  351. package/node_modules/pi-web-access/curator-server.ts +5 -1
  352. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  353. package/node_modules/pi-web-access/github-extract.ts +242 -1
  354. package/node_modules/pi-web-access/index.ts +118 -64
  355. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  356. package/node_modules/pi-web-access/package.json +2 -2
  357. package/node_modules/pi-web-access/perplexity.ts +14 -1
  358. package/node_modules/pi-web-access/utils.ts +23 -6
  359. package/node_modules/pi-web-access/xai-search.ts +96 -33
  360. package/package.json +6 -6
@@ -0,0 +1,281 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.ts";
3
+ import type { SearchOptions, SearchResponse, SearchResult } from "./perplexity.ts";
4
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
5
+ import { getWebSearchConfigPath } from "./utils.ts";
6
+
7
+ const MISTRAL_CONVERSATIONS_URL = "https://api.mistral.ai/v1/conversations";
8
+ const CONFIG_PATH = getWebSearchConfigPath();
9
+ const SEARCH_TIMEOUT_MS = 60_000;
10
+ const DEFAULT_SEARCH_MODEL = "mistral-small-latest";
11
+ const DEFAULT_SEARCH_TOOL = "web_search";
12
+ const MAX_RESULTS = 20;
13
+
14
+ type MistralSearchTool = "web_search" | "web_search_premium";
15
+
16
+ interface WebSearchConfig {
17
+ mistralApiKey?: unknown;
18
+ mistralSearchModel?: unknown;
19
+ mistralSearchTool?: unknown;
20
+ }
21
+
22
+ interface DomainFilters {
23
+ include: string[];
24
+ exclude: string[];
25
+ }
26
+
27
+ let cachedConfig: WebSearchConfig | null = null;
28
+
29
+ function loadConfig(): WebSearchConfig {
30
+ if (cachedConfig) return cachedConfig;
31
+ if (!existsSync(CONFIG_PATH)) {
32
+ cachedConfig = {};
33
+ return cachedConfig;
34
+ }
35
+
36
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
37
+ let parsed: unknown;
38
+ try {
39
+ parsed = JSON.parse(raw);
40
+ } catch (err) {
41
+ const message = err instanceof Error ? err.message : String(err);
42
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
43
+ }
44
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
45
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
46
+ }
47
+ cachedConfig = parsed as WebSearchConfig;
48
+ return cachedConfig;
49
+ }
50
+
51
+ function resolveSearchModel(value: unknown): string {
52
+ if (value === undefined) return DEFAULT_SEARCH_MODEL;
53
+ if (typeof value !== "string" || value.trim().length === 0) {
54
+ throw new Error(`mistralSearchModel in ${CONFIG_PATH} must be a non-empty string`);
55
+ }
56
+ return value.trim();
57
+ }
58
+
59
+ function resolveSearchTool(value: unknown): MistralSearchTool {
60
+ if (value === undefined) return DEFAULT_SEARCH_TOOL;
61
+ if (value !== "web_search" && value !== "web_search_premium") {
62
+ throw new Error(`mistralSearchTool in ${CONFIG_PATH} must be either web_search or web_search_premium`);
63
+ }
64
+ return value;
65
+ }
66
+
67
+ function normalizeCount(value: number | undefined): number {
68
+ if (typeof value !== "number" || !Number.isFinite(value)) return 5;
69
+ return Math.max(1, Math.min(Math.floor(value), MAX_RESULTS));
70
+ }
71
+
72
+ function normalizeDomain(value: string): string | null {
73
+ let input = value.trim().toLowerCase();
74
+ if (!input) return null;
75
+ if (input.startsWith("-")) input = input.slice(1).trim();
76
+ if (!input) return null;
77
+ try {
78
+ const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
79
+ input = parsed.hostname;
80
+ } catch {
81
+ input = input.split("/")[0]?.split(":")[0] ?? "";
82
+ }
83
+ input = input.replace(/^\.+|\.+$/g, "");
84
+ return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
85
+ }
86
+
87
+ function parseDomainFilters(domainFilter: string[] | undefined): DomainFilters {
88
+ const filters: DomainFilters = { include: [], exclude: [] };
89
+ for (const raw of domainFilter ?? []) {
90
+ const domain = normalizeDomain(raw);
91
+ if (!domain) continue;
92
+ const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
93
+ if (!target.includes(domain)) target.push(domain);
94
+ }
95
+ return filters;
96
+ }
97
+
98
+ function passesDomainFilters(url: string, filters: DomainFilters): boolean {
99
+ if (filters.include.length === 0 && filters.exclude.length === 0) return true;
100
+ const hostname = new URL(url).hostname.toLowerCase();
101
+ const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
102
+ if (filters.exclude.some(matches)) return false;
103
+ return filters.include.length === 0 || filters.include.some(matches);
104
+ }
105
+
106
+ function normalizeResultUrl(value: unknown): string | null {
107
+ if (typeof value !== "string") return null;
108
+ const url = value.trim();
109
+ if (!url) return null;
110
+ try {
111
+ const parsed = new URL(url);
112
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
113
+ } catch {
114
+ return null;
115
+ }
116
+ return url;
117
+ }
118
+
119
+ function buildSearchPrompt(query: string, options: SearchOptions, numResults: number): string {
120
+ const lines: string[] = [];
121
+ if (options.recencyFilter) {
122
+ const labels: Record<string, string> = {
123
+ day: "past 24 hours",
124
+ week: "past week",
125
+ month: "past month",
126
+ year: "past year",
127
+ };
128
+ lines.push(`Prefer sources from the ${labels[options.recencyFilter] ?? options.recencyFilter}.`);
129
+ }
130
+ if (typeof options.numResults === "number" && Number.isFinite(options.numResults) && options.numResults > 0) {
131
+ lines.push(`Prefer up to ${numResults} distinct sources.`);
132
+ }
133
+
134
+ const filters = parseDomainFilters(options.domainFilter);
135
+ if (filters.include.length > 0) lines.push(`Only use sources from: ${filters.include.slice(0, 100).join(", ")}.`);
136
+ if (filters.exclude.length > 0) lines.push(`Do not use sources from: ${filters.exclude.slice(0, 100).join(", ")}.`);
137
+ return lines.length > 0 ? `${lines.join(" ")}\n\n${query}` : query;
138
+ }
139
+
140
+ function errorMessage(err: unknown): string {
141
+ return err instanceof Error ? err.message : String(err);
142
+ }
143
+
144
+ function invalidResponse(message: string): Error {
145
+ return new Error(`Mistral API returned invalid response: ${message}`);
146
+ }
147
+
148
+ function parseConversationResponse(value: unknown, options: SearchOptions, numResults: number): SearchResponse {
149
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
150
+ throw invalidResponse("expected an object envelope");
151
+ }
152
+ const outputs = (value as Record<string, unknown>).outputs;
153
+ if (!Array.isArray(outputs)) throw invalidResponse("outputs must be an array");
154
+
155
+ const answerParts: string[] = [];
156
+ const results: SearchResult[] = [];
157
+ const seenUrls = new Set<string>();
158
+ const domainFilters = parseDomainFilters(options.domainFilter);
159
+
160
+ for (const output of outputs) {
161
+ if (!output || typeof output !== "object" || Array.isArray(output)) continue;
162
+ const entry = output as Record<string, unknown>;
163
+ if (entry.type !== "message.output") continue;
164
+ const content = entry.content;
165
+ if (typeof content === "string") {
166
+ if (content.trim()) answerParts.push(content.trim());
167
+ continue;
168
+ }
169
+ if (!Array.isArray(content)) continue;
170
+
171
+ for (const chunk of content) {
172
+ if (!chunk || typeof chunk !== "object" || Array.isArray(chunk)) continue;
173
+ const part = chunk as Record<string, unknown>;
174
+ if (part.type === "text") {
175
+ if (typeof part.text === "string" && part.text.trim()) answerParts.push(part.text.trim());
176
+ continue;
177
+ }
178
+ if (part.type !== "tool_reference" || results.length >= numResults) continue;
179
+
180
+ const url = normalizeResultUrl(part.url);
181
+ if (!url || !passesDomainFilters(url, domainFilters) || seenUrls.has(url)) continue;
182
+ seenUrls.add(url);
183
+ const title = typeof part.title === "string" && part.title.trim() ? part.title.trim() : url;
184
+ const snippet = typeof part.description === "string" ? part.description : "";
185
+ results.push({ title, url, snippet });
186
+ }
187
+ }
188
+
189
+ const answer = answerParts.join("\n").trim();
190
+ if (!answer && results.length === 0) throw invalidResponse("no answer or sources");
191
+ return { answer, results };
192
+ }
193
+
194
+ async function getApiKey(signal?: AbortSignal): Promise<string> {
195
+ const apiKey = await resolveCredential({
196
+ provider: "Mistral",
197
+ configuredValue: loadConfig().mistralApiKey,
198
+ environmentValue: process.env.MISTRAL_API_KEY,
199
+ signal,
200
+ });
201
+ if (!apiKey) {
202
+ throw new Error(
203
+ "Mistral API key not found. Either:\n" +
204
+ ` 1. Create ${CONFIG_PATH} with { "mistralApiKey": "your-key" }\n` +
205
+ " 2. Set MISTRAL_API_KEY environment variable\n" +
206
+ "Get a key at https://console.mistral.ai/api-keys",
207
+ );
208
+ }
209
+ return apiKey;
210
+ }
211
+
212
+ export function isMistralAvailable(): boolean {
213
+ try {
214
+ const config = loadConfig();
215
+ resolveSearchModel(config.mistralSearchModel);
216
+ resolveSearchTool(config.mistralSearchTool);
217
+ return hasCredentialSource({
218
+ provider: "Mistral",
219
+ configuredValue: config.mistralApiKey,
220
+ environmentValue: process.env.MISTRAL_API_KEY,
221
+ });
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
226
+
227
+ export async function searchWithMistral(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
228
+ const config = loadConfig();
229
+ const model = resolveSearchModel(config.mistralSearchModel);
230
+ const tool = resolveSearchTool(config.mistralSearchTool);
231
+ const numResults = normalizeCount(options.numResults);
232
+ const apiKey = await getApiKey(options.signal);
233
+ const requestSignal = options.signal
234
+ ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])
235
+ : AbortSignal.timeout(SEARCH_TIMEOUT_MS);
236
+ const activityId = activityMonitor.logStart({ type: "api", query });
237
+
238
+ try {
239
+ const response = await fetch(MISTRAL_CONVERSATIONS_URL, {
240
+ method: "POST",
241
+ headers: {
242
+ Authorization: `Bearer ${apiKey}`,
243
+ "Content-Type": "application/json",
244
+ },
245
+ body: JSON.stringify({
246
+ inputs: [{ role: "user", content: buildSearchPrompt(query, options, numResults) }],
247
+ stream: false,
248
+ model,
249
+ tools: [{ type: tool }],
250
+ }),
251
+ signal: requestSignal,
252
+ });
253
+
254
+ if (!response.ok) {
255
+ const errorText = redactCredential(await response.text(), apiKey);
256
+ throw new Error(`Mistral API error ${response.status}: ${errorText.slice(0, 300)}`);
257
+ }
258
+
259
+ let parsed: unknown;
260
+ try {
261
+ parsed = await response.json();
262
+ } catch (err) {
263
+ throw new Error(`Mistral API returned invalid JSON: ${errorMessage(err)}`);
264
+ }
265
+ const result = parseConversationResponse(parsed, options, numResults);
266
+ activityMonitor.logComplete(activityId, response.status);
267
+ return result;
268
+ } catch (err) {
269
+ const message = errorMessage(err);
270
+ const redactedMessage = redactCredential(message, apiKey);
271
+ if (redactedMessage.toLowerCase().includes("abort")) {
272
+ activityMonitor.logComplete(activityId, 0);
273
+ } else {
274
+ activityMonitor.logError(activityId, redactedMessage);
275
+ }
276
+ if (redactedMessage === message) throw err;
277
+ const redactedError = new Error(redactedMessage);
278
+ if (err instanceof Error) redactedError.name = err.name;
279
+ throw redactedError;
280
+ }
281
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-web-access",
3
- "version": "0.27.0",
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, Gemini, and Kimi.",
3
+ "version": "0.28.0",
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, Gemini, Kimi, and Mistral.",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "test": "node --test",
@@ -105,6 +105,18 @@ export function isPerplexityAvailable(): boolean {
105
105
  });
106
106
  }
107
107
 
108
+ /** Hard ceiling on kept citations, matching the `numResults` clamp. */
109
+ const MAX_CITATIONS = 20;
110
+
111
+ // Preserve citation numbering by keeping the prefix through the highest cited index, capped at 20.
112
+ function citationsToKeep(answer: string, available: number, numResults: number): number {
113
+ let highestCited = 0;
114
+ for (const match of answer.matchAll(/\[(\d{1,3})\]/g)) {
115
+ highestCited = Math.max(highestCited, Number(match[1]));
116
+ }
117
+ return Math.min(available, MAX_CITATIONS, Math.max(numResults, highestCited));
118
+ }
119
+
108
120
  export async function searchWithPerplexity(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
109
121
  checkRateLimit();
110
122
 
@@ -184,7 +196,8 @@ export async function searchWithPerplexity(query: string, options: SearchOptions
184
196
  const citations = Array.isArray(data.citations) ? data.citations : [];
185
197
 
186
198
  const results: SearchResult[] = [];
187
- for (let i = 0; i < Math.min(citations.length, numResults); i++) {
199
+ const citationCount = citationsToKeep(answer, citations.length, numResults);
200
+ for (let i = 0; i < citationCount; i++) {
188
201
  const citation = citations[i];
189
202
  if (typeof citation === "string") {
190
203
  results.push({ title: `Source ${i + 1}`, url: citation, snippet: "" });
@@ -5,10 +5,24 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
5
5
  import { homedir, hostname, tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
7
 
8
+ let cachedWebSearchConfigDir: string | undefined;
9
+
8
10
  export function getWebSearchConfigDir(): string {
9
- if (process.env.PI_CODING_AGENT_DIR) return process.env.PI_CODING_AGENT_DIR;
10
- if (process.env.XDG_CONFIG_HOME) return join(process.env.XDG_CONFIG_HOME, "pi");
11
- return join(homedir(), ".pi");
11
+ if (cachedWebSearchConfigDir) return cachedWebSearchConfigDir;
12
+
13
+ const explicitDir = process.env.PI_CODING_AGENT_DIR;
14
+ if (explicitDir) return cachedWebSearchConfigDir = explicitDir;
15
+
16
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
17
+ if (xdgConfigHome) {
18
+ const xdgDir = join(xdgConfigHome, "pi");
19
+ if (existsSync(join(xdgDir, "web-search.json"))) return cachedWebSearchConfigDir = xdgDir;
20
+
21
+ const legacyDir = join(homedir(), ".pi");
22
+ if (existsSync(join(legacyDir, "web-search.json"))) return cachedWebSearchConfigDir = legacyDir;
23
+ return cachedWebSearchConfigDir = xdgDir;
24
+ }
25
+ return cachedWebSearchConfigDir = join(homedir(), ".pi");
12
26
  }
13
27
 
14
28
  export function getWebSearchConfigPath(): string {
@@ -237,14 +251,17 @@ function loadConfiguredProxy(): string | null {
237
251
  }
238
252
 
239
253
  export function runWithProxy<T>(proxy: string | undefined, fn: () => T): T {
240
- if (proxy === undefined) return fn();
254
+ if (proxy === undefined) {
255
+ const configured = loadConfiguredProxy();
256
+ if (configured === null) return fn();
257
+ return proxyStorage.run(configured, fn);
258
+ }
241
259
  const normalized = normalizeProxyUrl(proxy, "proxy");
242
260
  return proxyStorage.run(normalized, fn);
243
261
  }
244
262
 
245
263
  export function getActiveProxy(): string | null {
246
- const scoped = proxyStorage.getStore();
247
- return scoped !== undefined ? scoped : loadConfiguredProxy();
264
+ return proxyStorage.getStore() ?? null;
248
265
  }
249
266
 
250
267
  export function hasScopedProxyDecision(): boolean {
@@ -5,8 +5,8 @@ import type { SearchOptions, SearchResponse, SearchResult } from "./perplexity.t
5
5
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
6
6
  import { getWebSearchConfigPath } from "./utils.ts";
7
7
 
8
- // xAI's Agent Tools API: a hosted `web_search` tool on an OpenAI-compatible
9
- // Responses endpoint. The search runs inside xAI's own inference, so — unlike
8
+ // xAI's Agent Tools API: hosted `web_search` and opt-in `x_search` tools on an
9
+ // OpenAI-compatible Responses endpoint. The search runs inside xAI's own inference, so — unlike
10
10
  // every keyed backend here — it is paid for by whatever credential answers for
11
11
  // the model, including a SuperGrok / X Premium subscription resolved through
12
12
  // pi's model registry.
@@ -19,8 +19,8 @@ import { getWebSearchConfigPath } from "./utils.ts";
19
19
  // is the shape verified against a live subscription account. `stream`,
20
20
  // `include`, `tool_choice` and `parallel_tool_calls` are all sent by the OpenAI
21
21
  // backend but were NOT verified here, and a 400 from an unsupported field would
22
- // cost the user their search. Sources come back in `web_search_call.action.sources`
23
- // without asking for them via `include`.
22
+ // cost the user their search. Responses API inline citations are enabled by
23
+ // default; sources may also come back in a search call's `action.sources`.
24
24
 
25
25
  const XAI_RESPONSES_URL = "https://api.x.ai/v1/responses";
26
26
  const CONFIG_PATH = getWebSearchConfigPath();
@@ -29,11 +29,18 @@ const SEARCH_TIMEOUT_MS = 60_000;
29
29
  // Ordered best-first. pi's builtin xai catalog is small and xAI retires models
30
30
  // briskly, so this is a preference list, not an assumption: the first one the
31
31
  // registry actually knows wins, and an unknown id is skipped rather than sent.
32
- const AUTH_MODEL_CANDIDATES = ["grok-4.5", "grok-4.3", "grok-build-0.1"] as const;
32
+ // xAI's current X Search docs use grok-4.6. Keep the existing web-only default
33
+ // unchanged, while preferring the documented model when X Search is opted in.
34
+ const WEB_SEARCH_AUTH_MODEL_CANDIDATES = ["grok-4.5", "grok-4.3", "grok-build-0.1"] as const;
35
+ const X_SEARCH_AUTH_MODEL_CANDIDATES = ["grok-4.6", ...WEB_SEARCH_AUTH_MODEL_CANDIDATES] as const;
36
+
37
+ type XaiSearchTool = "web_search" | "x_search";
38
+ const DEFAULT_XAI_SEARCH_TOOLS = ["web_search"] as const satisfies readonly XaiSearchTool[];
33
39
 
34
40
  interface WebSearchConfig {
35
41
  xaiApiKey?: unknown;
36
42
  xaiSearchModel?: unknown;
43
+ xaiSearchTools?: unknown;
37
44
  }
38
45
 
39
46
  type ProviderHeaders = Record<string, string | null>;
@@ -71,6 +78,29 @@ function resolveConfiguredSearchModel(value: unknown): string | undefined {
71
78
  return value.trim();
72
79
  }
73
80
 
81
+ function resolveConfiguredSearchTools(value: unknown): XaiSearchTool[] {
82
+ if (value === undefined) return [...DEFAULT_XAI_SEARCH_TOOLS];
83
+ if (!Array.isArray(value) || value.length === 0) {
84
+ throw new Error(`xaiSearchTools in ${CONFIG_PATH} must be a non-empty array containing only web_search or x_search`);
85
+ }
86
+
87
+ const tools: XaiSearchTool[] = [];
88
+ for (const tool of value) {
89
+ if (tool !== "web_search" && tool !== "x_search") {
90
+ throw new Error(`xaiSearchTools in ${CONFIG_PATH} may only contain web_search or x_search`);
91
+ }
92
+ if (tools.includes(tool)) {
93
+ throw new Error(`xaiSearchTools in ${CONFIG_PATH} must not contain duplicates: ${tool}`);
94
+ }
95
+ tools.push(tool);
96
+ }
97
+ return tools;
98
+ }
99
+
100
+ function modelCandidatesForTools(tools: readonly XaiSearchTool[]): readonly string[] {
101
+ return tools.includes("x_search") ? X_SEARCH_AUTH_MODEL_CANDIDATES : WEB_SEARCH_AUTH_MODEL_CANDIDATES;
102
+ }
103
+
74
104
  function toRequestHeaders(headers: ProviderHeaders): Record<string, string> {
75
105
  const requestHeaders: Record<string, string> = {};
76
106
  for (const [name, value] of Object.entries(headers)) {
@@ -84,8 +114,12 @@ function toRequestHeaders(headers: ProviderHeaders): Record<string, string> {
84
114
  * its own searches and no api key has to be configured at all. Mirrors how the
85
115
  * OpenAI backend picks up a Codex sign-in.
86
116
  */
87
- async function resolvePiAuth(ctx: ExtensionContext, modelOverride?: string): Promise<XaiAuth | undefined> {
88
- for (const modelId of AUTH_MODEL_CANDIDATES) {
117
+ async function resolvePiAuth(
118
+ ctx: ExtensionContext,
119
+ modelOverride: string | undefined,
120
+ tools: readonly XaiSearchTool[],
121
+ ): Promise<XaiAuth | undefined> {
122
+ for (const modelId of modelCandidatesForTools(tools)) {
89
123
  try {
90
124
  const model = ctx.modelRegistry.find("xai", modelId);
91
125
  if (!model) continue;
@@ -102,8 +136,9 @@ async function resolvePiAuth(ctx: ExtensionContext, modelOverride?: string): Pro
102
136
  export async function resolveXaiAuth(ctx?: ExtensionContext, signal?: AbortSignal): Promise<XaiAuth | undefined> {
103
137
  const config = loadConfig();
104
138
  const modelOverride = resolveConfiguredSearchModel(config.xaiSearchModel);
139
+ const tools = resolveConfiguredSearchTools(config.xaiSearchTools);
105
140
  if (ctx) {
106
- const auth = await resolvePiAuth(ctx, modelOverride);
141
+ const auth = await resolvePiAuth(ctx, modelOverride, tools);
107
142
  if (auth) return auth;
108
143
  }
109
144
 
@@ -119,12 +154,19 @@ export async function resolveXaiAuth(ctx?: ExtensionContext, signal?: AbortSigna
119
154
  environmentValue: process.env.XAI_API_KEY,
120
155
  signal,
121
156
  });
122
- return apiKey ? { apiKey, model: modelOverride ?? AUTH_MODEL_CANDIDATES[0], headers: {} } : undefined;
157
+ return apiKey ? { apiKey, model: modelOverride ?? modelCandidatesForTools(tools)[0], headers: {} } : undefined;
123
158
  }
124
159
 
125
160
  export async function isXaiSearchAvailable(ctx?: ExtensionContext): Promise<boolean> {
126
- if (ctx && await resolvePiAuth(ctx)) return true;
127
- const config = loadConfig();
161
+ let config: WebSearchConfig;
162
+ let tools: XaiSearchTool[];
163
+ try {
164
+ config = loadConfig();
165
+ tools = resolveConfiguredSearchTools(config.xaiSearchTools);
166
+ } catch {
167
+ return false;
168
+ }
169
+ if (ctx && await resolvePiAuth(ctx, undefined, tools)) return true;
128
170
  return hasCredentialSource({
129
171
  provider: "xAI",
130
172
  configuredValue: config.xaiApiKey,
@@ -153,9 +195,17 @@ function normalizeDomain(value: string): string | null {
153
195
  * an unknown field risks a 400 that costs the whole search; steering through
154
196
  * the instruction text degrades to "the model ignored it" instead.
155
197
  */
156
- function buildInput(query: string, options: SearchOptions): string {
198
+ function buildInput(query: string, options: SearchOptions, tools: readonly XaiSearchTool[]): string {
199
+ let searchInstruction: string;
200
+ if (tools.includes("x_search") && tools.includes("web_search")) {
201
+ searchInstruction = "Search the web and X and answer using only what the search results say.";
202
+ } else if (tools.includes("x_search")) {
203
+ searchInstruction = "Search X and answer using only what the X results say.";
204
+ } else {
205
+ searchInstruction = "Search the web and answer using only what the web results say.";
206
+ }
157
207
  const lines = [
158
- "Search the web and answer using only what the web results say.",
208
+ searchInstruction,
159
209
  "Cite your sources inline.",
160
210
  ];
161
211
 
@@ -198,6 +248,21 @@ function addResult(results: SearchResult[], seen: Set<string>, url: unknown, tit
198
248
  });
199
249
  }
200
250
 
251
+ function addSource(results: SearchResult[], seen: Set<string>, source: unknown): void {
252
+ if (typeof source === "string") {
253
+ addResult(results, seen, source, undefined);
254
+ return;
255
+ }
256
+ if (!source || typeof source !== "object") return;
257
+ const record = source as Record<string, unknown>;
258
+ addResult(results, seen, record.url ?? record.source_website_url, record.title ?? record.caption);
259
+ }
260
+
261
+ function addSources(results: SearchResult[], seen: Set<string>, sources: unknown): void {
262
+ if (!Array.isArray(sources)) return;
263
+ for (const source of sources) addSource(results, seen, source);
264
+ }
265
+
201
266
  function extractSnippetAround(text: string, start: unknown, end: unknown): string {
202
267
  if (typeof start !== "number" || typeof end !== "number" || !text) return "";
203
268
  const before = Math.max(0, start - 100);
@@ -222,13 +287,13 @@ function extractAnswer(output: unknown[]): string {
222
287
  }
223
288
 
224
289
  /**
225
- * Sources live in two places and neither is a top-level `citations` array:
226
- * `url_citation` annotations on the answer text (these carry titles and offsets,
227
- * so they go first and win the dedupe), and the raw `sources` each
228
- * `web_search_call` visited. A third-party extension that reads `data.citations`
229
- * silently returns answers with no sources at all — hence both paths here.
290
+ * Keep annotation sources first because they carry titles and offsets and must
291
+ * win URL deduplication. Raw source lists from web/X search calls are next,
292
+ * followed by xAI's response-level `citations` URL list. The latter is usually
293
+ * strings, but object entries are tolerated when a compatible gateway adds
294
+ * metadata.
230
295
  */
231
- function extractSearchResults(output: unknown[], numResults: number | undefined): SearchResult[] {
296
+ function extractSearchResults(output: unknown[], citations: unknown, numResults: number | undefined): SearchResult[] {
232
297
  const results: SearchResult[] = [];
233
298
  const seenUrls = new Set<string>();
234
299
 
@@ -256,21 +321,18 @@ function extractSearchResults(output: unknown[], numResults: number | undefined)
256
321
  }
257
322
 
258
323
  for (const item of output) {
259
- if (!item || typeof item !== "object" || (item as { type?: unknown }).type !== "web_search_call") continue;
324
+ if (!item || typeof item !== "object") continue;
325
+ const type = (item as { type?: unknown }).type;
326
+ if (type !== "web_search_call" && type !== "x_search_call") continue;
260
327
  const value = item as { action?: unknown; sources?: unknown; results?: unknown };
261
328
  const actionSources = value.action && typeof value.action === "object"
262
329
  ? (value.action as { sources?: unknown }).sources
263
330
  : undefined;
264
- for (const group of [actionSources, value.sources, value.results]) {
265
- if (!Array.isArray(group)) continue;
266
- for (const source of group) {
267
- if (!source || typeof source !== "object") continue;
268
- const record = source as Record<string, unknown>;
269
- addResult(results, seenUrls, record.url ?? record.source_website_url, record.title ?? record.caption);
270
- }
271
- }
331
+ for (const group of [actionSources, value.sources, value.results]) addSources(results, seenUrls, group);
272
332
  }
273
333
 
334
+ addSources(results, seenUrls, citations);
335
+
274
336
  if (typeof numResults === "number" && Number.isFinite(numResults) && numResults > 0) {
275
337
  return results.slice(0, Math.min(Math.floor(numResults), 20));
276
338
  }
@@ -282,10 +344,11 @@ export async function searchWithXai(
282
344
  options: SearchOptions = {},
283
345
  ctx?: ExtensionContext,
284
346
  ): Promise<SearchResponse> {
347
+ const tools = resolveConfiguredSearchTools(loadConfig().xaiSearchTools);
285
348
  const auth = await resolveXaiAuth(ctx, options.signal);
286
349
  if (!auth) {
287
350
  throw new Error(
288
- "xAI web search unavailable. Either:\n" +
351
+ "xAI search unavailable. Either:\n" +
289
352
  " 1. Use /login to sign in with a SuperGrok or X Premium subscription\n" +
290
353
  ` 2. Create ${CONFIG_PATH} with { "xaiApiKey": "your-key" }\n` +
291
354
  " 3. Set XAI_API_KEY environment variable",
@@ -303,8 +366,8 @@ export async function searchWithXai(
303
366
  },
304
367
  body: JSON.stringify({
305
368
  model: auth.model,
306
- input: buildInput(query, options),
307
- tools: [{ type: "web_search" }],
369
+ input: buildInput(query, options, tools),
370
+ tools: tools.map((type) => ({ type })),
308
371
  }),
309
372
  signal: options.signal
310
373
  ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])
@@ -326,10 +389,10 @@ export async function searchWithXai(
326
389
  }
327
390
  const output = Array.isArray(parsed.output) ? parsed.output : [];
328
391
  const answer = extractAnswer(output);
329
- const results = extractSearchResults(output, options.numResults);
392
+ const results = extractSearchResults(output, parsed.citations, options.numResults);
330
393
 
331
394
  if (!answer && results.length === 0) {
332
- throw new Error("xAI web_search returned no answer or sources");
395
+ throw new Error(`xAI ${tools.join("+")} returned no answer or sources`);
333
396
  }
334
397
 
335
398
  activityMonitor.logComplete(activityId, response.status);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-extensions",
3
- "version": "2026.9.0",
3
+ "version": "2026.9.2",
4
4
  "description": "Aggregate installer for HerbertGao-maintained Pi extensions.",
5
5
  "keywords": [
6
6
  "extensions",
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@ast-grep/cli": "^0.45.0",
39
39
  "@ast-grep/napi": "^0.45.0",
40
- "@czottmann/pi-automode": "1.15.0",
40
+ "@czottmann/pi-automode": "1.16.0",
41
41
  "@dietrichgebert/ponytail": "4.9.0",
42
42
  "@earendil-works/pi-coding-agent": "^0.84.4",
43
43
  "@earendil-works/pi-tui": "^0.84.4",
@@ -56,11 +56,11 @@
56
56
  "@modelcontextprotocol/sdk": "^1.29.0",
57
57
  "@mozilla/readability": "^0.6.0",
58
58
  "@napi-rs/keyring": "^1.3.0",
59
- "@narumitw/pi-btw": "0.57.0",
59
+ "@narumitw/pi-btw": "0.58.0",
60
60
  "@narumitw/pi-caffeinate": "0.49.7",
61
61
  "@narumitw/pi-tui-kit": "^0.59.0",
62
62
  "@noble/ed25519": "^3.1.0",
63
- "@pi-plugins/fast-mode": "0.1.10",
63
+ "@pi-plugins/fast-mode": "0.1.11",
64
64
  "@shikijs/cli": "^4.0.2",
65
65
  "@sinclair/typebox": "^0.34.49",
66
66
  "@tifan/pi-copy-response": "0.2.6",
@@ -90,9 +90,9 @@
90
90
  "open": "^10.2.0",
91
91
  "p-limit": "^6.1.0",
92
92
  "pi-footer": "0.5.1",
93
- "pi-lens": "4.1.3",
93
+ "pi-lens": "4.1.5",
94
94
  "pi-mcp-adapter": "2.32.1",
95
- "pi-web-access": "0.27.0",
95
+ "pi-web-access": "0.28.0",
96
96
  "pidusage": "^4.0.1",
97
97
  "promise.try": "^2.0.1",
98
98
  "qrcode-terminal": "^0.12.0",