@herbertgao/pi-extensions 2026.9.1 → 2026.9.3

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 (341) hide show
  1. package/README.md +3 -2
  2. package/THIRD_PARTY_NOTICES.md +24 -0
  3. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  4. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  5. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  6. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  7. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  8. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  9. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  10. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  11. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  12. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  13. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  14. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  15. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  16. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  17. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  18. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  19. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  20. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  38. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  39. package/node_modules/@narumitw/pi-btw/dist/index.ts +478 -43
  40. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  41. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  42. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  43. package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -3
  44. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  45. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  46. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  47. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  48. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  49. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  50. package/node_modules/pi-lens/CHANGELOG.md +260 -0
  51. package/node_modules/pi-lens/README.md +13 -8
  52. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  53. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -7
  54. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  55. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  56. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -1
  57. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -8
  58. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  59. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -1
  60. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  61. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -6
  62. package/node_modules/pi-lens/dist/clients/bootstrap.js +4 -4
  63. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  64. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -5
  65. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +1 -9
  66. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -6
  67. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +2 -2
  68. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -7
  69. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  70. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  71. package/node_modules/pi-lens/dist/clients/config-core/index.js +11 -8
  72. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +1 -1
  73. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +2 -9
  74. package/node_modules/pi-lens/dist/clients/config-core/records.js +1 -1
  75. package/node_modules/pi-lens/dist/clients/config-core/schema.js +3 -5
  76. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +3 -1
  77. package/node_modules/pi-lens/dist/clients/config-resolve.js +10 -7
  78. package/node_modules/pi-lens/dist/clients/config-schema.js +56 -2
  79. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  80. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  81. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -4
  82. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  83. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  84. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +1 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  86. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -41
  87. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +18 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  91. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  93. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +3 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +7 -2
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +3 -2
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +3 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +15 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +3 -3
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +3 -2
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +5 -3
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +1 -64
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  146. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +5 -3
  147. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  148. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  149. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -7
  150. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  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 -0
  153. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  154. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  155. package/node_modules/pi-lens/dist/clients/file-time.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +5 -1
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  158. package/node_modules/pi-lens/dist/clients/format-service.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/formatters.js +66 -93
  160. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +3 -4
  161. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  162. package/node_modules/pi-lens/dist/clients/git-guard.js +5 -0
  163. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  164. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  165. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  166. package/node_modules/pi-lens/dist/clients/installer/index.js +416 -61
  167. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  168. package/node_modules/pi-lens/dist/clients/instance-reaper.js +6 -6
  169. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  170. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  171. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -11
  172. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  173. package/node_modules/pi-lens/dist/clients/latency-logger.js +7 -7
  174. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -1
  175. package/node_modules/pi-lens/dist/clients/lens-config.js +23 -0
  176. package/node_modules/pi-lens/dist/clients/lens-engine.js +3 -4
  177. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +18 -0
  179. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  180. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -36
  181. package/node_modules/pi-lens/dist/clients/lsp/client.js +4 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/config.js +37 -7
  183. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  184. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +79 -1
  185. package/node_modules/pi-lens/dist/clients/lsp/edits.js +3 -3
  186. package/node_modules/pi-lens/dist/clients/lsp/index.js +491 -64
  187. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  188. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -8
  189. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -2
  191. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  192. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +5 -5
  194. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  195. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  197. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  198. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  200. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  201. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +1 -1
  202. package/node_modules/pi-lens/dist/clients/module-report.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/mutating-tool.js +3 -3
  204. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +3 -3
  205. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -4
  206. package/node_modules/pi-lens/dist/clients/observed-mutation.js +9 -9
  207. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +2 -6
  208. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  209. package/node_modules/pi-lens/dist/clients/opengrep-config.js +1 -1
  210. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/path-utils.js +23 -2
  212. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  213. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +9 -4
  214. package/node_modules/pi-lens/dist/clients/pipeline.js +5 -4
  215. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  216. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  217. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  218. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  219. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  220. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  221. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  222. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  223. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +1 -1
  224. package/node_modules/pi-lens/dist/clients/project-lens-config.js +49 -1
  225. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  226. package/node_modules/pi-lens/dist/clients/python-provenance.js +1 -1
  227. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +3 -1
  228. package/node_modules/pi-lens/dist/clients/read-guard.js +5 -2
  229. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  230. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +3 -5
  231. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  232. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  233. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  234. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +10 -1
  235. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  236. package/node_modules/pi-lens/dist/clients/runtime-session.js +58 -14
  237. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +11 -1
  238. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  239. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  240. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -11
  241. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  242. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  243. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  244. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  245. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  246. package/node_modules/pi-lens/dist/clients/sgconfig.js +1 -5
  247. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  248. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  249. package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
  250. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  251. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  252. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +77 -72
  253. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  254. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  255. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  256. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  257. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  258. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +1 -52
  259. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  260. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  261. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  262. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -4
  263. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  264. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -7
  265. package/node_modules/pi-lens/dist/clients/word-index-store.js +3 -7
  266. package/node_modules/pi-lens/dist/clients/word-index.js +9 -16
  267. package/node_modules/pi-lens/dist/clients/zizmor-config.js +1 -1
  268. package/node_modules/pi-lens/dist/index.js +19969 -17481
  269. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  270. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  271. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  272. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +1 -1
  273. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +1 -1
  274. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  275. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  276. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  277. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  278. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  279. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  280. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +354 -86
  281. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +54 -112
  282. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  283. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  284. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  285. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  286. package/node_modules/pi-lens/dist/tools/render-compact.js +281 -2
  287. package/node_modules/pi-lens/dist/tools/symbol-search.js +3 -4
  288. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  289. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  290. package/node_modules/pi-lens/docs/configuration.md +9 -0
  291. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  292. package/node_modules/pi-lens/docs/features.md +5 -5
  293. package/node_modules/pi-lens/docs/globalconfig.md +31 -1
  294. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  295. package/node_modules/pi-lens/docs/mcp.md +13 -1
  296. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  297. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  298. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  299. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +41 -0
  300. package/node_modules/pi-lens/docs/pi-lens-subagent.md +21 -2
  301. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  302. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  303. package/node_modules/pi-lens/docs/release-qa-baseline.md +17 -5
  304. package/node_modules/pi-lens/docs/servercapabilities.md +2 -3
  305. package/node_modules/pi-lens/docs/settings.md +37 -2
  306. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  307. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  308. package/node_modules/pi-lens/docs/usage.md +13 -3
  309. package/node_modules/pi-lens/docs/word-index.md +1 -1
  310. package/node_modules/pi-lens/package.json +15 -15
  311. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  312. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  313. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +1 -1
  314. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  315. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  316. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  317. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  318. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  319. package/node_modules/pi-web-access/README.md +33 -22
  320. package/node_modules/pi-web-access/abortable.ts +17 -0
  321. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  322. package/node_modules/pi-web-access/credential-source.ts +1 -0
  323. package/node_modules/pi-web-access/curator-page.ts +20 -2
  324. package/node_modules/pi-web-access/curator-run.ts +44 -0
  325. package/node_modules/pi-web-access/curator-server.ts +3 -1
  326. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  327. package/node_modules/pi-web-access/extract.ts +57 -12
  328. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  329. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  330. package/node_modules/pi-web-access/index.ts +25 -18
  331. package/node_modules/pi-web-access/openai-search.ts +35 -13
  332. package/node_modules/pi-web-access/package.json +2 -2
  333. package/node_modules/pi-web-access/page-query.ts +5 -2
  334. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  335. package/node_modules/pi-web-access/serpapi.ts +220 -0
  336. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  337. package/node_modules/pi-web-access/storage.ts +10 -0
  338. package/node_modules/pi-web-access/summary-review.ts +34 -21
  339. package/node_modules/pi-web-access/utils.ts +6 -4
  340. package/package.json +7 -4
  341. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -41,6 +41,8 @@ interface WebSearchConfig {
41
41
 
42
42
  type ProviderHeaders = Record<string, string | null>;
43
43
 
44
+ class CustomOpenAIBaseUrlError extends Error {}
45
+
44
46
  interface OpenAIAuth {
45
47
  provider: string;
46
48
  apiKey: string;
@@ -215,7 +217,7 @@ function toRequestHeaders(headers: ProviderHeaders): Record<string, string> {
215
217
  return requestHeaders;
216
218
  }
217
219
 
218
- async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, providers: readonly string[], modelOverride?: string): Promise<OpenAIAuth | undefined> {
220
+ async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, providers: readonly string[], modelOverride?: string, hasExplicitResponsesUrl = false): Promise<OpenAIAuth | undefined> {
219
221
  let models: ReturnType<typeof ctx.modelRegistry.getAll>;
220
222
  try {
221
223
  models = ctx.modelRegistry.getAll();
@@ -225,19 +227,34 @@ async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, provid
225
227
  for (const provider of providers) {
226
228
  const preferred = pickSearchModel(models.filter((model) => model.provider === provider));
227
229
  if (!preferred) continue;
230
+ let resolved: Awaited<ReturnType<typeof ctx.modelRegistry.getApiKeyAndHeaders>>;
228
231
  try {
229
- const resolved = await ctx.modelRegistry.getApiKeyAndHeaders(preferred);
230
- if (resolved.ok && resolved.apiKey) {
231
- return {
232
- provider,
233
- apiKey: resolved.apiKey,
234
- model: modelOverride ?? preferred.id,
235
- headers: resolved.headers ?? {},
236
- responsesUrl,
237
- };
238
- }
232
+ resolved = await ctx.modelRegistry.getApiKeyAndHeaders(preferred);
239
233
  } catch {
234
+ continue;
235
+ }
236
+ if (!resolved.ok || !resolved.apiKey) continue;
237
+ // Auth can override the model's base URL. Do not guess Responses/web_search
238
+ // support from a gateway URL, or pair its credential with the official API.
239
+ const baseUrl = resolved.baseUrl ?? preferred.baseUrl;
240
+ const useCodexEndpoint = provider === "openai-codex" || isCodexJwt(resolved.apiKey);
241
+ if (!hasExplicitResponsesUrl && !useCodexEndpoint && baseUrl !== undefined) {
242
+ let isOfficial = false;
243
+ try {
244
+ isOfficial = new URL(baseUrl).toString().replace(/\/+$/u, "") === "https://api.openai.com/v1";
245
+ } catch {
246
+ }
247
+ if (!isOfficial) {
248
+ throw new CustomOpenAIBaseUrlError(`OpenAI web search cannot reuse Pi credentials with a custom baseUrl by default. Set openaiResponsesUrl in ${CONFIG_PATH} to the full Responses endpoint for this credential.`);
249
+ }
240
250
  }
251
+ return {
252
+ provider,
253
+ apiKey: resolved.apiKey,
254
+ model: modelOverride ?? preferred.id,
255
+ headers: resolved.headers ?? {},
256
+ responsesUrl,
257
+ };
241
258
  }
242
259
  return undefined;
243
260
  }
@@ -248,7 +265,7 @@ export async function resolveOpenAIAuth(ctx?: ExtensionContext, signal?: AbortSi
248
265
  const modelOverride = resolveConfiguredSearchModel(config.openaiSearchModel);
249
266
  const providers = resolveConfiguredSearchProviders(config.openaiSearchProviders);
250
267
  if (ctx) {
251
- const auth = await resolvePiAuth(ctx, responsesUrl, providers, modelOverride);
268
+ const auth = await resolvePiAuth(ctx, responsesUrl, providers, modelOverride, config.openaiResponsesUrl !== undefined);
252
269
  if (auth) return auth;
253
270
  }
254
271
 
@@ -273,7 +290,12 @@ export async function isOpenAISearchAvailable(ctx?: ExtensionContext): Promise<b
273
290
  const config = loadConfig();
274
291
  const responsesUrl = resolveConfiguredResponsesUrl(config.openaiResponsesUrl);
275
292
  const providers = resolveConfiguredSearchProviders(config.openaiSearchProviders);
276
- if (ctx && await resolvePiAuth(ctx, responsesUrl, providers)) return true;
293
+ try {
294
+ if (ctx && await resolvePiAuth(ctx, responsesUrl, providers, undefined, config.openaiResponsesUrl !== undefined)) return true;
295
+ } catch (err) {
296
+ if (err instanceof CustomOpenAIBaseUrlError) return false;
297
+ throw err;
298
+ }
277
299
  return hasCredentialSource({
278
300
  provider: "OpenAI",
279
301
  configuredValue: config.openaiApiKey,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-web-access",
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.",
3
+ "version": "0.29.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, SerpApi, SearXNG, Exa, Perplexity, Gemini, Kimi, and Mistral.",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "test": "node --test",
@@ -1,8 +1,9 @@
1
- import { complete, type Api, type Message, type Model } from "@earendil-works/pi-ai/compat";
1
+ import type { complete, Api, Message, Model } from "@earendil-works/pi-ai/compat";
2
2
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
5
5
  import { getWebSearchConfigPath } from "./utils.ts";
6
+ import { awaitWithAbort } from "./abortable.ts";
6
7
 
7
8
  const OUTPUT_TOKENS = 2_000;
8
9
  const INPUT_CONTEXT_FRACTION = 0.6;
@@ -111,7 +112,9 @@ export async function answerFromPage(
111
112
  if (!auth.ok || !auth.apiKey) throw new Error(`No API key available for answer model ${model.provider}/${model.id}`);
112
113
  const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { complete?: typeof complete };
113
114
  const usesRegistryComplete = typeof registry.complete === "function";
114
- const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : complete;
115
+ if (signal?.aborted) throw new Error("Aborted");
116
+ const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : (await awaitWithAbort(import("@earendil-works/pi-ai/compat"), signal)).complete;
117
+ if (signal?.aborted) throw new Error("Aborted");
115
118
 
116
119
  const contextTokens = model.contextWindow > 0 ? model.contextWindow : FALLBACK_CONTEXT_TOKENS;
117
120
  const maximumInputTokens = Math.max(1, Math.min(
@@ -1,5 +1,6 @@
1
- import { complete, type Api, type Model, type ProviderHeaders } from "@earendil-works/pi-ai/compat";
1
+ import type { complete, Api, Model, ProviderHeaders } from "@earendil-works/pi-ai/compat";
2
2
  import type { SummaryGenerationContext } from "./summary-review.ts";
3
+ import { awaitWithAbort } from "./abortable.ts";
3
4
  import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
4
5
 
5
6
  async function resolveFirstAvailableModel(
@@ -28,7 +29,9 @@ export async function rewriteSearchQuery(
28
29
  ]);
29
30
  const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { complete?: typeof complete };
30
31
  const usesRegistryComplete = typeof registry.complete === "function";
31
- const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : complete;
32
+ if (signal.aborted) throw new Error("Aborted");
33
+ const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : (await awaitWithAbort(import("@earendil-works/pi-ai/compat"), signal)).complete;
34
+ if (signal.aborted) throw new Error("Aborted");
32
35
  const response = await completeFn(
33
36
  model,
34
37
  {
@@ -0,0 +1,220 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.ts";
3
+ import type { SearchOptions, SearchResponse } from "./perplexity.ts";
4
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
5
+ import { getWebSearchConfigPath } from "./utils.ts";
6
+
7
+ const SERPAPI_SEARCH_URL = "https://serpapi.com/search.json";
8
+ const CONFIG_PATH = getWebSearchConfigPath();
9
+ const SEARCH_TIMEOUT_MS = 60_000;
10
+ const RECENCY_TBS: Record<NonNullable<SearchOptions["recencyFilter"]>, string> = {
11
+ day: "qdr:d",
12
+ week: "qdr:w",
13
+ month: "qdr:m",
14
+ year: "qdr:y",
15
+ };
16
+
17
+ interface WebSearchConfig {
18
+ serpapiApiKey?: unknown;
19
+ }
20
+
21
+ interface SerpApiOrganicResult {
22
+ title?: unknown;
23
+ link?: unknown;
24
+ snippet?: unknown;
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
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
36
+ let parsed: unknown;
37
+ try {
38
+ parsed = JSON.parse(raw);
39
+ } catch (err) {
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
42
+ }
43
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
44
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
45
+ }
46
+ cachedConfig = parsed as WebSearchConfig;
47
+ return cachedConfig;
48
+ }
49
+
50
+ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
51
+ return resolveCredential({
52
+ provider: "SerpApi",
53
+ configuredValue: loadConfig().serpapiApiKey,
54
+ environmentValue: process.env.SERPAPI_KEY,
55
+ signal,
56
+ });
57
+ }
58
+
59
+ async function requireApiKey(signal?: AbortSignal): Promise<string> {
60
+ const apiKey = await getApiKey(signal);
61
+ if (!apiKey) {
62
+ throw new Error(
63
+ "SerpApi API key not found. Either:\n" +
64
+ ` 1. Create ${CONFIG_PATH} with { "serpapiApiKey": "your-key" }\n` +
65
+ " 2. Set SERPAPI_KEY environment variable\n" +
66
+ "Get a key at https://serpapi.com/manage-api-key",
67
+ );
68
+ }
69
+ return apiKey;
70
+ }
71
+
72
+ function normalizeCount(value: number | undefined): number {
73
+ if (typeof value !== "number" || !Number.isFinite(value)) return 5;
74
+ return Math.max(1, Math.min(Math.floor(value), 20));
75
+ }
76
+
77
+ interface DomainFilters {
78
+ include: string[];
79
+ exclude: string[];
80
+ }
81
+
82
+ function normalizeDomain(value: string): string | null {
83
+ let input = value.trim().toLowerCase();
84
+ if (!input) return null;
85
+ if (input.startsWith("-")) input = input.slice(1).trim();
86
+ if (!input) return null;
87
+ try {
88
+ const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
89
+ input = parsed.hostname;
90
+ } catch {
91
+ input = input.split("/")[0]?.split(":")[0] ?? "";
92
+ }
93
+ input = input.replace(/^\.+|\.+$/g, "");
94
+ return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
95
+ }
96
+
97
+ function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
98
+ const filters: DomainFilters = { include: [], exclude: [] };
99
+ for (const raw of domainFilter ?? []) {
100
+ const domain = normalizeDomain(raw);
101
+ if (!domain) continue;
102
+ const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
103
+ if (!target.includes(domain)) target.push(domain);
104
+ }
105
+ return filters;
106
+ }
107
+
108
+ function passesDomainFilters(url: string, filters: DomainFilters): boolean {
109
+ if (filters.include.length === 0 && filters.exclude.length === 0) return true;
110
+ let hostname: string;
111
+ try {
112
+ hostname = new URL(url).hostname.toLowerCase();
113
+ } catch {
114
+ return false;
115
+ }
116
+ const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
117
+ if (filters.exclude.some(matches)) return false;
118
+ return filters.include.length === 0 || filters.include.some(matches);
119
+ }
120
+
121
+ function buildQuery(query: string, filters: DomainFilters): string {
122
+ const parts = [query];
123
+ if (filters.include.length === 1) parts.push(`site:${filters.include[0]}`);
124
+ if (filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(" OR ")})`);
125
+ for (const domain of filters.exclude) parts.push(`-site:${domain}`);
126
+ return parts.join(" ");
127
+ }
128
+
129
+ function errorMessage(err: unknown): string {
130
+ return err instanceof Error ? err.message : String(err);
131
+ }
132
+
133
+ function invalidResponse(message: string): Error {
134
+ return new Error(`SerpApi returned invalid response: ${message}`);
135
+ }
136
+
137
+ function parseResponse(value: unknown): SerpApiOrganicResult[] {
138
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidResponse("expected an object envelope");
139
+ const envelope = value as Record<string, unknown>;
140
+ if (typeof envelope.error === "string" && envelope.error.trim()) throw invalidResponse(envelope.error.trim());
141
+ if (!Array.isArray(envelope.organic_results)) throw invalidResponse("expected organic_results array");
142
+ return envelope.organic_results as SerpApiOrganicResult[];
143
+ }
144
+
145
+ function buildAnswer(results: SearchResponse["results"]): string {
146
+ return results.map((result) => result.snippet
147
+ ? `${result.snippet}\nSource: ${result.title} (${result.url})`
148
+ : `Source: ${result.title} (${result.url})`).join("\n\n");
149
+ }
150
+
151
+ export function isSerpApiAvailable(): boolean {
152
+ return hasCredentialSource({ provider: "SerpApi", configuredValue: loadConfig().serpapiApiKey, environmentValue: process.env.SERPAPI_KEY });
153
+ }
154
+
155
+ export async function searchWithSerpApi(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
156
+ const apiKey = await requireApiKey(options.signal);
157
+ const numResults = normalizeCount(options.numResults);
158
+ const filters = parseDomainFilter(options.domainFilter);
159
+ const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
160
+ const url = new URL(SERPAPI_SEARCH_URL);
161
+ url.searchParams.set("engine", "google");
162
+ url.searchParams.set("q", buildQuery(query, filters));
163
+ url.searchParams.set("api_key", apiKey);
164
+ url.searchParams.set("num", String(requestCount));
165
+ if (options.recencyFilter) url.searchParams.set("tbs", RECENCY_TBS[options.recencyFilter]);
166
+ const activityId = activityMonitor.logStart({ type: "api", query });
167
+ const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
168
+ let response: Response;
169
+ let entries: SerpApiOrganicResult[];
170
+ try {
171
+ response = await fetch(url, {
172
+ headers: { Accept: "application/json" },
173
+ signal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,
174
+ });
175
+ if (!response.ok) {
176
+ const errorText = await response.text();
177
+ throw new Error(`SerpApi error ${response.status}: ${redactCredential(errorText, apiKey).slice(0, 300)}`);
178
+ }
179
+ let rawData: unknown;
180
+ try {
181
+ rawData = await response.json();
182
+ } catch (err) {
183
+ if (err instanceof Error && err.name === "TimeoutError") throw err;
184
+ throw new Error(`SerpApi returned invalid JSON: ${errorMessage(err)}`);
185
+ }
186
+ entries = parseResponse(rawData);
187
+ } catch (err) {
188
+ if (options.signal?.aborted) {
189
+ activityMonitor.logComplete(activityId, 0);
190
+ throw new Error("Aborted");
191
+ }
192
+ const message = errorMessage(err);
193
+ const providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === "TimeoutError");
194
+ const outgoing = providerTimeout
195
+ ? new Error(`SerpApi request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)
196
+ : (() => {
197
+ const redactedMessage = redactCredential(message, apiKey);
198
+ if (redactedMessage === message && err instanceof Error) return err;
199
+ const redactedError = new Error(redactedMessage);
200
+ if (err instanceof Error) redactedError.name = err.name;
201
+ return redactedError;
202
+ })();
203
+ activityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));
204
+ throw outgoing;
205
+ }
206
+ activityMonitor.logComplete(activityId, response.status);
207
+ const results: SearchResponse["results"] = [];
208
+ for (const entry of entries) {
209
+ if (!entry || typeof entry !== "object") continue;
210
+ if (typeof entry.link !== "string" || !entry.link) continue;
211
+ if (!passesDomainFilters(entry.link, filters)) continue;
212
+ results.push({
213
+ title: typeof entry.title === "string" && entry.title.trim() ? entry.title.trim() : `Source ${results.length + 1}`,
214
+ url: entry.link,
215
+ snippet: typeof entry.snippet === "string" ? entry.snippet : "",
216
+ });
217
+ if (results.length >= numResults) break;
218
+ }
219
+ return { answer: buildAnswer(results), results };
220
+ }
@@ -232,6 +232,7 @@ export async function fetchRemoteUrl(
232
232
  const fetchImpl = options.fetch ?? fetch;
233
233
  const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
234
234
  let current = await validateRemoteUrl(url, options);
235
+ const configuredOrigin = current.origin;
235
236
  let requestInit = init;
236
237
 
237
238
  for (let redirects = 0; redirects <= maxRedirects; redirects++) {
@@ -243,7 +244,10 @@ export async function fetchRemoteUrl(
243
244
  if (redirects === maxRedirects) throw new Error(`Too many redirects fetching ${current.toString()}`);
244
245
 
245
246
  const from = current;
246
- current = await validateRemoteUrl(new URL(location, current), options);
247
+ const next = new URL(location, current);
248
+ // allowLoopback exempts an explicitly configured endpoint, never a redirect target: a loopback
249
+ // base must not be able to pivot the request onto a different loopback origin.
250
+ current = await validateRemoteUrl(next, next.origin === configuredOrigin ? options : { ...options, allowLoopback: false });
247
251
  if (response.status === 303 || ((response.status === 301 || response.status === 302) && requestInit.method?.toUpperCase() === "POST")) {
248
252
  const { body: _body, ...nextInit } = requestInit;
249
253
  requestInit = { ...nextInit, method: "GET" };
@@ -426,8 +426,17 @@ function readCachedFetchData(data: StoredSearchData, now = Date.now()): StoredSe
426
426
  }
427
427
  }
428
428
 
429
+ function pruneExpiredFetchedResults(now: number): void {
430
+ for (const [id, data] of storedResults) {
431
+ if (data.type === "fetch" && now - data.timestamp >= CACHE_TTL_MS) {
432
+ storedResults.set(id, unavailableFetchData(data, "Cached fetched content is missing or expired"));
433
+ }
434
+ }
435
+ }
436
+
429
437
  export function pruneExpiredFetchCache(now = Date.now(), requestedLimits?: Partial<FetchCacheLimits>): void {
430
438
  const limits = cacheLimits(requestedLimits);
439
+ pruneExpiredFetchedResults(now);
431
440
  try { pruneFetchCache(now, limits); } catch {}
432
441
  }
433
442
 
@@ -436,6 +445,7 @@ export function storeResult(id: string, data: StoredSearchData): void {
436
445
  }
437
446
 
438
447
  export function storeFetchedContentResult(id: string, data: StoredSearchData & { type: "fetch"; urls: ExtractedContent[] }): StoredSearchData {
448
+ pruneExpiredFetchedResults(Date.now());
439
449
  let ref: FetchCacheRef | null = null;
440
450
  let cacheError: string | undefined;
441
451
  try {
@@ -1,5 +1,5 @@
1
- import { clampThinkingLevel, type ModelThinkingLevel, type ThinkingLevel } from "@earendil-works/pi-ai";
2
- import { complete, completeSimple, type Api, type Message, type Model } from "@earendil-works/pi-ai/compat";
1
+ import type { ModelThinkingLevel, ThinkingLevel } from "@earendil-works/pi-ai";
2
+ import type { complete, Api, Message, Model } from "@earendil-works/pi-ai/compat";
3
3
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
4
  import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix, type SummaryThinkingLevel } from "./summary-model-scope.ts";
5
5
  import type { QueryResultData } from "./storage.ts";
@@ -204,8 +204,9 @@ function parseModelSelector(value: string): { provider: string; id: string; thin
204
204
  };
205
205
  }
206
206
 
207
- function resolveThinkingLevel(model: Model<Api>, requested?: SummaryThinkingLevel): ModelThinkingLevel | undefined {
207
+ async function resolveThinkingLevel(model: Model<Api>, requested?: SummaryThinkingLevel): Promise<ModelThinkingLevel | undefined> {
208
208
  if (!requested) return undefined;
209
+ const { clampThinkingLevel } = await import("@earendil-works/pi-ai");
209
210
  return clampThinkingLevel(model, requested as ModelThinkingLevel);
210
211
  }
211
212
 
@@ -295,22 +296,21 @@ export async function generateSummaryDraft(
295
296
  if (!ctx || !ctx.modelRegistry) {
296
297
  throw new Error("Summary generation context unavailable");
297
298
  }
299
+ if (signal?.aborted) throw new Error("Aborted");
298
300
 
299
301
  const registry = ctx.modelRegistry as SummaryModelRegistry;
300
302
  const customCompleteFn = completeFn !== undefined;
301
303
  const usesRegistryComplete = !customCompleteFn && typeof registry.complete === "function";
302
- completeFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : complete;
304
+ let piAiCompat: typeof import("@earendil-works/pi-ai/compat") | undefined;
303
305
 
304
306
  const generationStartedAt = Date.now();
305
307
  const deadlineController = new AbortController();
306
308
  const deadlineMarker = Symbol("summary-generation-deadline");
307
309
  let deadlineTimer: ReturnType<typeof setTimeout> | undefined;
308
- let resolveDeadline!: () => void;
309
310
  const deadlinePromise = new Promise<typeof deadlineMarker>(resolve => {
310
- resolveDeadline = () => resolve(deadlineMarker);
311
311
  deadlineTimer = setTimeout(() => {
312
312
  deadlineController.abort();
313
- resolveDeadline();
313
+ resolve(deadlineMarker);
314
314
  }, deadlineMs);
315
315
  });
316
316
 
@@ -326,17 +326,24 @@ export async function generateSummaryDraft(
326
326
  ? AbortSignal.any([signal, deadlineController.signal])
327
327
  : deadlineController.signal;
328
328
 
329
+ function checkSummaryDeadline(): void {
330
+ if (signal?.aborted) throw new Error("Aborted");
331
+ // Imports or synchronous providers can finish before an overdue timer runs.
332
+ if (deadlineController.signal.aborted || Date.now() - generationStartedAt >= deadlineMs) {
333
+ deadlineController.abort();
334
+ throw deadlineMarker;
335
+ }
336
+ }
337
+
329
338
  async function raceSummaryOperation<T>(operation: Promise<T>): Promise<T> {
330
339
  // A provider may ignore AbortSignal and never settle; observe it before racing so
331
340
  // its eventual rejection cannot become an unhandled promise rejection.
332
341
  void operation.then(() => undefined, () => undefined);
342
+ checkSummaryDeadline();
333
343
  const contenders: Promise<unknown>[] = [operation, deadlinePromise];
334
344
  if (callerAbortPromise) contenders.push(callerAbortPromise);
335
345
  const result = await Promise.race(contenders);
336
- if (result === deadlineMarker) {
337
- if (signal?.aborted) throw new Error("Aborted");
338
- throw deadlineMarker;
339
- }
346
+ checkSummaryDeadline();
340
347
  return result as T;
341
348
  }
342
349
 
@@ -345,12 +352,14 @@ export async function generateSummaryDraft(
345
352
  const prompt = buildSummaryPrompt(results, feedback);
346
353
  let resolved: Awaited<ReturnType<typeof resolveSummaryModelCandidates>>;
347
354
  try {
355
+ checkSummaryDeadline();
356
+ if (!customCompleteFn && !usesRegistryComplete) {
357
+ piAiCompat = await raceSummaryOperation(import("@earendil-works/pi-ai/compat"));
358
+ }
359
+ completeFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : piAiCompat!.complete;
348
360
  resolved = await raceSummaryOperation(resolveSummaryModelCandidates(ctx, modelOverride));
349
361
  } catch (err) {
350
- if (signal?.aborted) throw new Error("Aborted");
351
- if (err === deadlineMarker || deadlineController.signal.aborted) {
352
- return buildFallbackSummary(results, "summary-generation-timeout", Date.now() - generationStartedAt);
353
- }
362
+ checkSummaryDeadline();
354
363
  const message = err instanceof Error ? err.message : String(err);
355
364
  return buildFallbackSummary(results, `summary-model-settings-error: ${message}`, Date.now() - generationStartedAt);
356
365
  }
@@ -364,7 +373,7 @@ export async function generateSummaryDraft(
364
373
  content: [{ type: "text", text: prompt }],
365
374
  timestamp: Date.now(),
366
375
  };
367
- const requestedThinkingLevel = resolveThinkingLevel(model, thinkingLevel);
376
+ const requestedThinkingLevel = await raceSummaryOperation(resolveThinkingLevel(model, thinkingLevel));
368
377
  const enabledThinkingLevel = requestedThinkingLevel && requestedThinkingLevel !== "off"
369
378
  ? requestedThinkingLevel as ThinkingLevel
370
379
  : undefined;
@@ -374,8 +383,9 @@ export async function generateSummaryDraft(
374
383
  ...(requestedThinkingLevel ? { reasoning: requestedThinkingLevel } : {}),
375
384
  ...(enabledThinkingLevel ? { reasoningEffort: enabledThinkingLevel } : {}),
376
385
  };
386
+ checkSummaryDeadline();
377
387
  const completion = thinkingLevel !== undefined && !customCompleteFn && !usesRegistryComplete
378
- ? completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })
388
+ ? piAiCompat!.completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })
379
389
  : completeFn(model, { messages: [userMessage] }, completionOptions);
380
390
 
381
391
  const response = await raceSummaryOperation(Promise.resolve(completion));
@@ -408,10 +418,7 @@ export async function generateSummaryDraft(
408
418
  },
409
419
  };
410
420
  } catch (err) {
411
- if (signal?.aborted) throw new Error("Aborted");
412
- if (err === deadlineMarker || deadlineController.signal.aborted) {
413
- return buildFallbackSummary(results, "summary-generation-timeout", Date.now() - generationStartedAt);
414
- }
421
+ checkSummaryDeadline();
415
422
  if (isAbortError(err)) throw err;
416
423
  lastError = err instanceof Error ? err.message : String(err);
417
424
  }
@@ -422,6 +429,12 @@ export async function generateSummaryDraft(
422
429
  lastError ? `summary-model-unavailable: ${lastError}` : "summary-model-unavailable",
423
430
  Date.now() - generationStartedAt,
424
431
  );
432
+ } catch (err) {
433
+ if (signal?.aborted) throw new Error("Aborted");
434
+ if (err === deadlineMarker) {
435
+ return buildFallbackSummary(results, "summary-generation-timeout", Date.now() - generationStartedAt);
436
+ }
437
+ throw err;
425
438
  } finally {
426
439
  if (deadlineTimer) clearTimeout(deadlineTimer);
427
440
  if (signal && callerAbortListener) signal.removeEventListener("abort", callerAbortListener);
@@ -22,7 +22,7 @@ export function getWebSearchConfigDir(): string {
22
22
  if (existsSync(join(legacyDir, "web-search.json"))) return cachedWebSearchConfigDir = legacyDir;
23
23
  return cachedWebSearchConfigDir = xdgDir;
24
24
  }
25
- return cachedWebSearchConfigDir = join(homedir(), ".pi");
25
+ return cachedWebSearchConfigDir = join(homedir(), ".pi", "agent");
26
26
  }
27
27
 
28
28
  export function getWebSearchConfigPath(): string {
@@ -208,7 +208,7 @@ const proxyStorage = new AsyncLocalStorage<string | null>();
208
208
 
209
209
  export function normalizeProxyUrl(value: unknown, source: string): string | null {
210
210
  if (value === undefined || value === null) return null;
211
- if (typeof value !== "string") throw new Error(`${source} must be an http(s) proxy URL string`);
211
+ if (typeof value !== "string") throw new Error(`${source} must be an http(s) or socks proxy URL string`);
212
212
  const trimmed = value.trim();
213
213
  if (!trimmed) return null;
214
214
  let parsed: URL;
@@ -217,8 +217,10 @@ export function normalizeProxyUrl(value: unknown, source: string): string | null
217
217
  } catch {
218
218
  throw new Error(`${source} must be a valid proxy URL: ${JSON.stringify(trimmed)}`);
219
219
  }
220
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
221
- throw new Error(`${source} must use the http:// or https:// scheme: ${trimmed}`);
220
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" &&
221
+ parsed.protocol !== "socks4:" && parsed.protocol !== "socks4a:" &&
222
+ parsed.protocol !== "socks5:" && parsed.protocol !== "socks5h:") {
223
+ throw new Error(`${source} must use the http://, https://, or socks scheme: ${trimmed}`);
222
224
  }
223
225
  if (!parsed.hostname) throw new Error(`${source} must include a proxy host: ${trimmed}`);
224
226
  parsed.hash = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-extensions",
3
- "version": "2026.9.1",
3
+ "version": "2026.9.3",
4
4
  "description": "Aggregate installer for HerbertGao-maintained Pi extensions.",
5
5
  "keywords": [
6
6
  "extensions",
@@ -47,6 +47,7 @@
47
47
  "@herbertgao/pi-cc-extensions": "0.9.0",
48
48
  "@herbertgao/pi-subagents": "0.17.1",
49
49
  "@herbertgao/resume-from": "0.2.0",
50
+ "@herbertgao/sol-pi": "0.2.0",
50
51
  "@juicesharp/rpiv-ask-user-question": "2.9.0",
51
52
  "@juicesharp/rpiv-config": "^2.9.0",
52
53
  "@luxusai/pi-hindsight": "0.12.0",
@@ -56,7 +57,7 @@
56
57
  "@modelcontextprotocol/sdk": "^1.29.0",
57
58
  "@mozilla/readability": "^0.6.0",
58
59
  "@napi-rs/keyring": "^1.3.0",
59
- "@narumitw/pi-btw": "0.57.0",
60
+ "@narumitw/pi-btw": "0.58.1",
60
61
  "@narumitw/pi-caffeinate": "0.49.7",
61
62
  "@narumitw/pi-tui-kit": "^0.59.0",
62
63
  "@noble/ed25519": "^3.1.0",
@@ -90,9 +91,9 @@
90
91
  "open": "^10.2.0",
91
92
  "p-limit": "^6.1.0",
92
93
  "pi-footer": "0.5.1",
93
- "pi-lens": "4.1.4",
94
+ "pi-lens": "4.1.6",
94
95
  "pi-mcp-adapter": "2.32.1",
95
- "pi-web-access": "0.28.0",
96
+ "pi-web-access": "0.29.0",
96
97
  "pidusage": "^4.0.1",
97
98
  "promise.try": "^2.0.1",
98
99
  "qrcode-terminal": "^0.12.0",
@@ -119,6 +120,7 @@
119
120
  "@herbertgao/pi-cc-extensions",
120
121
  "@herbertgao/pi-subagents",
121
122
  "@herbertgao/resume-from",
123
+ "@herbertgao/sol-pi",
122
124
  "@juicesharp/rpiv-ask-user-question",
123
125
  "@luxusai/pi-hindsight",
124
126
  "@narumitw/pi-btw",
@@ -144,6 +146,7 @@
144
146
  "extensions": [
145
147
  "./node_modules/@dietrichgebert/ponytail/pi-extension/index.js",
146
148
  "./node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts",
149
+ "./node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts",
147
150
  "./node_modules/@tifan/pi-copy-response/src/index.ts",
148
151
  "./node_modules/@herbertgao/resume-from/shims/pi/extensions/resume-from.js",
149
152
  "./node_modules/@tifan/pi-handoff/src/index.ts",