@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
@@ -43,6 +43,7 @@ function buildProviderButtons(
43
43
  { value: "mistral", label: "Mistral", available: available.mistral },
44
44
  { value: "brightdata", label: "Bright Data", available: available.brightdata },
45
45
  { value: "serpbase", label: "SerpBase", available: available.serpbase },
46
+ { value: "serpapi", label: "SerpApi", available: available.serpapi },
46
47
  { value: "serper", label: "Serper", available: available.serper },
47
48
  { value: "valyu", label: "Valyu", available: available.valyu },
48
49
  ];
@@ -149,6 +150,7 @@ ${CSS}
149
150
  <button class="btn btn-secondary" id="btn-summary-regenerate">Regenerate</button>
150
151
  <button class="btn btn-secondary" id="btn-summary-preview" title="Preview rendered summary">Preview</button>
151
152
  <button class="btn btn-submit" id="btn-summary-approve">Approve</button>
153
+ <button class="btn btn-submit" id="btn-summary-approve-remaining">Approve + auto-summary remaining searches for this prompt</button>
152
154
  </div>
153
155
  </section>
154
156
  </main>
@@ -1197,6 +1199,7 @@ main {
1197
1199
  }
1198
1200
  .summary-actions {
1199
1201
  display: flex;
1202
+ flex-wrap: wrap;
1200
1203
  align-items: center;
1201
1204
  justify-content: flex-end;
1202
1205
  gap: 8px;
@@ -1469,7 +1472,7 @@ const SCRIPT = `(function() {
1469
1472
  var token = DATA.sessionToken;
1470
1473
  var timeoutSec = DATA.timeout;
1471
1474
  var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
1472
- var providers = ["all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serper", "valyu"];
1475
+ var providers = ["all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "valyu"];
1473
1476
  var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
1474
1477
  var workflow = "summary-review";
1475
1478
  var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
@@ -1534,6 +1537,7 @@ const SCRIPT = `(function() {
1534
1537
  var btnSummaryRegenerate = document.getElementById("btn-summary-regenerate");
1535
1538
  var btnSummaryPreview = document.getElementById("btn-summary-preview");
1536
1539
  var btnSummaryApprove = document.getElementById("btn-summary-approve");
1540
+ var btnSummaryApproveRemaining = document.getElementById("btn-summary-approve-remaining");
1537
1541
  var successOverlay = document.getElementById("success-overlay");
1538
1542
  var successText = document.getElementById("success-text");
1539
1543
  var expiredOverlay = document.getElementById("expired-overlay");
@@ -1699,6 +1703,9 @@ const SCRIPT = `(function() {
1699
1703
  if (provider === "mistral") return "Mistral";
1700
1704
  if (provider === "brightdata") return "Bright Data";
1701
1705
  if (provider === "serpbase") return "SerpBase";
1706
+ if (provider === "serpapi") return "SerpApi";
1707
+ if (provider === "serper") return "Serper";
1708
+ if (provider === "valyu") return "Valyu";
1702
1709
  return "Unknown";
1703
1710
  }
1704
1711
 
@@ -2114,6 +2121,9 @@ const SCRIPT = `(function() {
2114
2121
  if (btnSummaryApprove) {
2115
2122
  btnSummaryApprove.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating || !hasSelection || !hasDraft;
2116
2123
  }
2124
+ if (btnSummaryApproveRemaining) {
2125
+ btnSummaryApproveRemaining.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating || !hasSelection || !hasDraft;
2126
+ }
2117
2127
 
2118
2128
  applyProviderInterlocks();
2119
2129
  }
@@ -3205,7 +3215,7 @@ const SCRIPT = `(function() {
3205
3215
  requestSummary(selected);
3206
3216
  }
3207
3217
 
3208
- function doApprove() {
3218
+ function doApprove(autoApproveRemainingSearches) {
3209
3219
  if (submitted || timerExpired || submitInFlight || stage !== "summary-review") return;
3210
3220
 
3211
3221
  var selected = getSelectedIndices();
@@ -3217,6 +3227,7 @@ const SCRIPT = `(function() {
3217
3227
 
3218
3228
  var draft = getSummaryDraftText();
3219
3229
  var payload = { selected: selected };
3230
+ if (autoApproveRemainingSearches === true) payload.autoApproveRemainingSearches = true;
3220
3231
  if (draft.length > 0) {
3221
3232
  payload.summary = draft;
3222
3233
  payload.summaryMeta = normalizeSummaryMeta(summaryMeta, summaryMeta && summaryMeta.edited === true);
@@ -3455,6 +3466,13 @@ const SCRIPT = `(function() {
3455
3466
  });
3456
3467
  }
3457
3468
 
3469
+ if (btnSummaryApproveRemaining) {
3470
+ btnSummaryApproveRemaining.addEventListener("click", function() {
3471
+ doApprove(true);
3472
+ resetTimer();
3473
+ });
3474
+ }
3475
+
3458
3476
  if (summaryInput) {
3459
3477
  summaryInput.addEventListener("input", function() {
3460
3478
  if (!summaryMeta || typeof summaryMeta !== "object") {
@@ -0,0 +1,44 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
3
+ export type WebSearchWorkflow = "none" | "summary-review" | "auto-summary";
4
+
5
+ export function resolveWebSearchWorkflow(input: unknown, hasUI: boolean): WebSearchWorkflow {
6
+ const normalized = typeof input === "string" ? input.trim().toLowerCase() : "";
7
+ if (normalized === "auto-summary") return "auto-summary";
8
+ if (!hasUI) return "none";
9
+ if (normalized === "none") return "none";
10
+ return "summary-review";
11
+ }
12
+
13
+ export class CuratorRunState {
14
+ private autoApproveRemaining = false;
15
+
16
+ approveRemainingSearches(): void {
17
+ this.autoApproveRemaining = true;
18
+ }
19
+
20
+ reset(): void {
21
+ this.autoApproveRemaining = false;
22
+ }
23
+
24
+ resolve(requestedWorkflow: unknown, configuredWorkflow: unknown, hasUI: boolean): WebSearchWorkflow {
25
+ const inherited = requestedWorkflow === undefined;
26
+ const workflow = resolveWebSearchWorkflow(
27
+ inherited ? configuredWorkflow : requestedWorkflow,
28
+ hasUI,
29
+ );
30
+ return inherited && this.autoApproveRemaining && workflow === "summary-review"
31
+ ? "auto-summary"
32
+ : workflow;
33
+ }
34
+ }
35
+
36
+ export function registerCuratorRunLifecycle(pi: Pick<ExtensionAPI, "on">): CuratorRunState {
37
+ const state = new CuratorRunState();
38
+ pi.on("before_agent_start", () => state.reset());
39
+ pi.on("agent_settled", () => state.reset());
40
+ pi.on("session_start", () => state.reset());
41
+ pi.on("session_tree", () => state.reset());
42
+ pi.on("session_shutdown", () => state.reset());
43
+ return state;
44
+ }
@@ -41,7 +41,7 @@ export interface IndexedCuratorSearchEntry extends CuratorSearchEntry {
41
41
  }
42
42
 
43
43
  export interface CuratorServerCallbacks {
44
- onSubmit: (payload: { selectedQueryIndices: number[]; summary?: string; summaryMeta?: SummaryMeta; rawResults?: boolean }) => void;
44
+ onSubmit: (payload: { selectedQueryIndices: number[]; summary?: string; summaryMeta?: SummaryMeta; rawResults?: boolean; autoApproveRemainingSearches?: boolean }) => void;
45
45
  onCancel: (reason: "user" | "timeout" | "stale") => void;
46
46
  onProviderChange: (provider: string) => void;
47
47
  onAddSearch: (query: string, provider?: string) => Promise<CuratorSearchEntry[]>;
@@ -642,12 +642,14 @@ export function startCuratorServer(
642
642
  return;
643
643
  }
644
644
  const rawResults = (body as { rawResults?: unknown }).rawResults === true;
645
+ const autoApproveRemainingSearches = (body as { autoApproveRemainingSearches?: unknown }).autoApproveRemainingSearches === true;
645
646
  sendJson(res, 200, { ok: true });
646
647
  setImmediate(() => callbacks.onSubmit({
647
648
  selectedQueryIndices: parsed.indices,
648
649
  ...(summary !== undefined ? { summary } : {}),
649
650
  ...(summaryMeta !== undefined ? { summaryMeta } : {}),
650
651
  rawResults,
652
+ ...(autoApproveRemainingSearches ? { autoApproveRemainingSearches: true } : {}),
651
653
  }));
652
654
  return;
653
655
  }
@@ -1,4 +1,3 @@
1
- import { parseHTML } from "linkedom";
2
1
  import { activityMonitor } from "./activity.ts";
3
2
  import type { SearchOptions, SearchResult, SearchResponse } from "./perplexity.ts";
4
3
 
@@ -88,6 +87,7 @@ export async function searchWithDuckDuckGo(query: string, options: SearchOptions
88
87
  throw new Error(`DuckDuckGo search error ${response.status}: ${body.slice(0, 300)}`);
89
88
  }
90
89
 
90
+ const { parseHTML } = await import("linkedom");
91
91
  const { document } = parseHTML(await response.text());
92
92
  const filters = normalizeDomainFilters(options.domainFilter);
93
93
  const results: SearchResult[] = [];
@@ -1,8 +1,5 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { Readability } from "@mozilla/readability";
3
- import { resizeImage } from "@earendil-works/pi-coding-agent";
4
- import { parseHTML } from "linkedom";
5
- import TurndownService from "turndown";
2
+ import type TurndownService from "turndown";
6
3
  import pLimit from "p-limit";
7
4
  import { activityMonitor } from "./activity.ts";
8
5
  import { extractRSCContent } from "./rsc-extract.ts";
@@ -20,6 +17,7 @@ import { extractWithQuerit, isQueritAvailable } from "./querit.ts";
20
17
  import { extractWithKagi, isKagiExtractAvailable } from "./kagi.ts";
21
18
  import { extractWithOllama, isOllamaFetchAvailable } from "./ollama.ts";
22
19
  import { extractWithFirecrawl, isFirecrawlAvailable } from "./firecrawl.ts";
20
+ import { extractWithCrawl4ai, isCrawl4aiAvailable } from "./crawl4ai.ts";
23
21
  import { extractWithBrightDataUnlocker, isBrightDataUnlockerAvailable } from "./brightdata-unlocker.ts";
24
22
  import { isVideoFile, extractVideo, extractVideoFrame, getLocalVideoDuration } from "./video-extract.ts";
25
23
  import { appendDeclaredWebLinks, discoverDeclaredWebLinks, type DeclaredWebLink } from "./declared-web-links.ts";
@@ -70,10 +68,10 @@ function loadFetchTimeoutMs(): number {
70
68
  const NON_RECOVERABLE_ERRORS = ["Unsupported content type", "Response too large", "PDF extraction is disabled", "Image fetching is disabled"];
71
69
  const MIN_USEFUL_CONTENT = 500;
72
70
  const SUPPORTED_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
73
- const FETCH_PROVIDERS = ["http", "firecrawl", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "parallel-mcp", "brightdata", "gemini"] as const;
71
+ const FETCH_PROVIDERS = ["http", "firecrawl", "crawl4ai", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "parallel-mcp", "brightdata", "gemini"] as const;
74
72
  type FetchProvider = typeof FETCH_PROVIDERS[number];
75
73
  type FetchRouting = { providers: FetchProvider[]; allowRemoteHostedProviders: boolean };
76
- const DEFAULT_FETCH_PROVIDER_ORDER: FetchProvider[] = ["http", "firecrawl", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "brightdata", "gemini"];
74
+ const DEFAULT_FETCH_PROVIDER_ORDER: FetchProvider[] = ["http", "firecrawl", "crawl4ai", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "brightdata", "gemini"];
77
75
  const REMOTE_HOSTED_FETCH_PROVIDERS = new Set<FetchProvider>(["jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "parallel-mcp", "brightdata", "gemini"]);
78
76
 
79
77
  function isDefuddleConsoleError(args: Parameters<typeof console.error>): boolean {
@@ -83,6 +81,7 @@ function isDefuddleConsoleError(args: Parameters<typeof console.error>): boolean
83
81
 
84
82
  async function extractWithDefuddle(text: string, url: string): Promise<{ title: string; content: string } | null> {
85
83
  const { Defuddle } = await import("defuddle/node");
84
+ const { parseHTML } = await import("linkedom");
86
85
  const { document } = parseHTML(text);
87
86
  Object.defineProperty(document, "location", {
88
87
  value: new URL(url),
@@ -135,6 +134,10 @@ function isAbortError(err: unknown): boolean {
135
134
  return errorMessage(err).toLowerCase().includes("abort");
136
135
  }
137
136
 
137
+ function isAbortException(err: unknown): boolean {
138
+ return err instanceof DOMException && (err.name === "AbortError" || err.name === "TimeoutError");
139
+ }
140
+
138
141
  function isRedirectPolicyError(message: string): boolean {
139
142
  return message.startsWith("Authenticated fetch refused cross-origin redirect") ||
140
143
  message.startsWith("Blocked internal ") ||
@@ -288,10 +291,19 @@ function abortedResult(url: string): ExtractedContent {
288
291
  return { url, title: "", content: "", error: "Aborted" };
289
292
  }
290
293
 
291
- const turndown = new TurndownService({
292
- headingStyle: "atx",
293
- codeBlockStyle: "fenced",
294
- });
294
+ // Share first-use initialization without loading Turndown at extension startup.
295
+ let turndownInstance: Promise<TurndownService> | undefined;
296
+ async function loadTurndown(): Promise<TurndownService> {
297
+ const { default: TurndownService } = await import("turndown");
298
+ return new TurndownService({
299
+ headingStyle: "atx",
300
+ codeBlockStyle: "fenced",
301
+ });
302
+ }
303
+ function getTurndown(): Promise<TurndownService> {
304
+ turndownInstance ??= loadTurndown();
305
+ return turndownInstance;
306
+ }
295
307
 
296
308
  const fetchLimit = pLimit(CONCURRENT_LIMIT);
297
309
 
@@ -329,7 +341,7 @@ export interface ExtractOptions {
329
341
  answerModel?: string;
330
342
  authFetchProfile?: AuthFetchProfile;
331
343
  toolNames?: RegisteredToolNames;
332
- /** Optional http(s) proxy URL; routed through the curl-backed transport. */
344
+ /** Optional HTTP(S) or SOCKS proxy URL; routed through the curl-backed transport. */
333
345
  proxy?: string;
334
346
  /** Custom DNS resolver used for SSRF validation. Primarily a test seam. */
335
347
  lookup?: Lookup;
@@ -788,6 +800,7 @@ export async function extractContent(
788
800
  };
789
801
 
790
802
  let firecrawlError: string | null = null;
803
+ let crawl4aiError: string | null = null;
791
804
  let tinyfishError: string | null = null;
792
805
  let search1apiError: string | null = null;
793
806
  let queritError: string | null = null;
@@ -830,6 +843,25 @@ export async function extractContent(
830
843
  continue;
831
844
  }
832
845
 
846
+ if (provider === "crawl4ai") {
847
+ try {
848
+ if (isCrawl4aiAvailable()) {
849
+ const ssrf = loadSsrfConfig();
850
+ const crawl4aiResult = await extractWithCrawl4ai(url, signal, {
851
+ timeoutMs: options?.timeoutMs,
852
+ ...(options?.lookup ? { lookup: options.lookup } : {}),
853
+ ssrf,
854
+ });
855
+ if (crawl4aiResult) return withDeclaredLinks(crawl4aiResult);
856
+ }
857
+ } catch (err) {
858
+ if (signal?.aborted || isAbortException(err)) return abortedResult(url);
859
+ crawl4aiError = errorMessage(err);
860
+ if (isConfigParseError(err)) return parseErrorResult(crawl4aiError);
861
+ }
862
+ continue;
863
+ }
864
+
833
865
  if (provider === "jina") {
834
866
  const jinaResult = await extractWithJinaReader(url, fetchTimeoutMs, signal, options?.lookup);
835
867
  if (jinaResult) return withDeclaredLinks(jinaResult);
@@ -991,6 +1023,7 @@ export async function extractContent(
991
1023
  const guidance = [
992
1024
  finalHttpResult?.error ?? "No fetch_content provider returned content",
993
1025
  ...(firecrawlError ? [`Firecrawl fallback failed: ${firecrawlError}`] : []),
1026
+ ...(crawl4aiError ? [`Crawl4AI fallback failed: ${crawl4aiError}`] : []),
994
1027
  ...(tinyfishError ? [`TinyFish fallback failed: ${tinyfishError}`] : []),
995
1028
  ...(search1apiError ? [`Search1API fallback failed: ${search1apiError}`] : []),
996
1029
  ...(queritError ? [`Querit fallback failed: ${queritError}`] : []),
@@ -1002,6 +1035,7 @@ export async function extractContent(
1002
1035
  "",
1003
1036
  "Fallback options:",
1004
1037
  ` • Set firecrawlBaseUrl in ${WEB_SEARCH_CONFIG_PATH} to a self-hosted Firecrawl instance`,
1038
+ ` • Set crawl4aiBaseUrl in ${WEB_SEARCH_CONFIG_PATH} to a self-hosted Crawl4AI instance`,
1005
1039
  ` • Set tinyfishApiKey in ${WEB_SEARCH_CONFIG_PATH} or TINYFISH_API_KEY`,
1006
1040
  ` • Set search1apiApiKey in ${WEB_SEARCH_CONFIG_PATH} or SEARCH1API_KEY`,
1007
1041
  ` • Set queritApiKey in ${WEB_SEARCH_CONFIG_PATH} or QUERIT_API_KEY`,
@@ -1118,6 +1152,7 @@ async function extractViaHttp(
1118
1152
  const activityId = activityMonitor.logStart({ type: "fetch", url });
1119
1153
 
1120
1154
  const controller = new AbortController();
1155
+ const startedAt = Date.now();
1121
1156
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1122
1157
 
1123
1158
  const onAbort = () => controller.abort();
@@ -1210,6 +1245,7 @@ async function extractViaHttp(
1210
1245
  }
1211
1246
  try {
1212
1247
  const buffer = await readResponseBufferWithLimit(response, maxResponseSize, () => responseSizeLimitError(maxResponseSize));
1248
+ const { resizeImage } = await import("@earendil-works/pi-coding-agent");
1213
1249
  const resized = await resizeImage(new Uint8Array(buffer), mimeType, { maxWidth: 2000, maxHeight: 2000 });
1214
1250
  activityMonitor.logComplete(activityId, response.status);
1215
1251
  if (!resized) return { url, title: "", content: "", error: `Could not decode image: ${mimeType}`, mimeType, status: response.status };
@@ -1278,6 +1314,7 @@ async function extractViaHttp(
1278
1314
  return { url, title, content: text, error: null };
1279
1315
  }
1280
1316
 
1317
+ const { parseHTML } = await import("linkedom");
1281
1318
  const { document } = parseHTML(text);
1282
1319
  const documentTitle = document.title?.trim() ?? "";
1283
1320
  const declaredLinks = discoverDeclaredWebLinks(
@@ -1285,6 +1322,7 @@ async function extractViaHttp(
1285
1322
  response.headers.get("link"),
1286
1323
  response.url || url,
1287
1324
  );
1325
+ const { Readability } = await import("@mozilla/readability");
1288
1326
  const reader = new Readability(document as unknown as Document);
1289
1327
  const article = reader.parse();
1290
1328
 
@@ -1332,7 +1370,7 @@ async function extractViaHttp(
1332
1370
  if (typeof article.content !== "string") {
1333
1371
  throw new Error("Readability returned invalid article content");
1334
1372
  }
1335
- const markdown = turndown.turndown(article.content);
1373
+ const markdown = (await getTurndown()).turndown(article.content);
1336
1374
  activityMonitor.logComplete(activityId, response.status);
1337
1375
 
1338
1376
  if (markdown.length < MIN_USEFUL_CONTENT) {
@@ -1387,6 +1425,13 @@ async function extractViaHttp(
1387
1425
  } finally {
1388
1426
  clearTimeout(timeoutId);
1389
1427
  signal?.removeEventListener("abort", onAbort);
1428
+ // Imports and CPU-bound processing need not observe the fetch signal, and
1429
+ // can finish before an expired timer gets a turn. Guard every exit, with
1430
+ // caller cancellation taking precedence over the internal deadline.
1431
+ if (signal?.aborted) return abortedResult(url);
1432
+ if (controller.signal.aborted || Date.now() - startedAt >= timeoutMs) {
1433
+ return { url, title: "", content: "", error: "The operation was aborted." };
1434
+ }
1390
1435
  }
1391
1436
  }
1392
1437
 
@@ -96,7 +96,7 @@ function normalizeAuth(value: unknown): true | string | undefined {
96
96
 
97
97
  function normalizeProxy(value: unknown): string | undefined {
98
98
  if (value === undefined || value === false) return undefined;
99
- if (value === null) throw new Error("proxy must be an http(s) proxy URL string");
99
+ if (value === null) throw new Error("proxy must be an http(s) or socks proxy URL string");
100
100
  const normalized = normalizeProxyUrl(value, "proxy");
101
101
  return normalized ?? "";
102
102
  }
@@ -34,13 +34,14 @@ import { isXcrawlAvailable, searchWithXCrawl } from "./xcrawl.ts";
34
34
  import { isXaiSearchAvailable, searchWithXai } from "./xai-search.ts";
35
35
  import { isBrightDataAvailable, searchWithBrightData } from "./brightdata.ts";
36
36
  import { isSerpBaseAvailable, searchWithSerpBase } from "./serpbase.ts";
37
+ import { isSerpApiAvailable, searchWithSerpApi } from "./serpapi.ts";
37
38
  import { isSerperAvailable, searchWithSerper } from "./serper.ts";
38
39
  import { isValyuAvailable, searchWithValyu } from "./valyu.ts";
39
40
  import { isKimiSearchAvailable, searchWithKimi } from "./kimi-search.ts";
40
41
  import { isMistralAvailable, searchWithMistral } from "./mistral-search.ts";
41
42
  import { getWebSearchConfigPath } from "./utils.ts";
42
43
 
43
- export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "mistral", "brightdata", "serpbase", "serper", "valyu", "bocha", "xcrawl"] as const;
44
+ export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "valyu", "bocha", "xcrawl"] as const;
44
45
  export const SEARCH_PROVIDERS = ["auto", "all", ...RESOLVED_SEARCH_PROVIDERS] as const;
45
46
 
46
47
  export type ResolvedSearchProvider = typeof RESOLVED_SEARCH_PROVIDERS[number];
@@ -105,7 +106,7 @@ export interface AttributedSearchResponse extends SearchResponse {
105
106
 
106
107
  const CONFIG_PATH = getWebSearchConfigPath();
107
108
  const DEFAULT_SEARCH_MODEL = "gemini-3.6-flash";
108
- // Explicit-only providers (Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, xAI, Mistral, Bright Data, SerpBase, Serper, Valyu) are deliberately absent:
109
+ // Explicit-only providers (Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Valyu) are deliberately absent:
109
110
  // `all` must never fan out to an opt-in or paid provider without the user asking for it.
110
111
  export const ALL_SEARCH_PROVIDERS: ResolvedSearchProvider[] = ["searxng", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "ollama", "perplexity", "gemini", "bocha"];
111
112
  const VALID_ROUTING_KINDS = ["transient", "quota", "network", "invalid-response", "unsupported"] as const;
@@ -311,7 +312,7 @@ function classifyProviderError(provider: ResolvedSearchProvider, err: unknown):
311
312
  kind = "unsupported";
312
313
  } else if (status === 400 || status === 422) {
313
314
  kind = "invalid-request";
314
- } else if (status === 402 || status === 429) {
315
+ } else if (status === 402 || status === 429 || (provider === "tavily" && status === 432)) {
315
316
  kind = "quota";
316
317
  } else if (status !== undefined && (status === 408 || status === 425 || status >= 500)) {
317
318
  kind = "transient";
@@ -364,6 +365,7 @@ async function searchWithResolvedProvider(
364
365
  if (provider === "mistral") return { ...(await searchWithMistral(query, options)), provider };
365
366
  if (provider === "brightdata") return { ...(await searchWithBrightData(query, options)), provider };
366
367
  if (provider === "serpbase") return { ...(await searchWithSerpBase(query, options)), provider };
368
+ if (provider === "serpapi") return { ...(await searchWithSerpApi(query, options)), provider };
367
369
  if (provider === "serper") return { ...(await searchWithSerper(query, options)), provider };
368
370
  if (provider === "valyu") return { ...(await searchWithValyu(query, options)), provider };
369
371
  if (provider === "xcrawl") return { ...(await searchWithXCrawl(query, options)), provider };
@@ -412,6 +414,7 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
412
414
  if (provider === "mistral") return isMistralAvailable();
413
415
  if (provider === "brightdata") return isBrightDataAvailable();
414
416
  if (provider === "serpbase") return isSerpBaseAvailable();
417
+ if (provider === "serpapi") return isSerpApiAvailable();
415
418
  if (provider === "serper") return isSerperAvailable();
416
419
  if (provider === "valyu") return isValyuAvailable();
417
420
  if (provider === "xcrawl") return isXcrawlAvailable();
@@ -451,6 +454,7 @@ function providerLabel(provider: ResolvedSearchProvider): string {
451
454
  if (provider === "mistral") return "Mistral";
452
455
  if (provider === "brightdata") return "Bright Data";
453
456
  if (provider === "serpbase") return "SerpBase";
457
+ if (provider === "serpapi") return "SerpApi";
454
458
  if (provider === "serper") return "Serper";
455
459
  if (provider === "valyu") return "Valyu";
456
460
  return provider.charAt(0).toUpperCase() + provider.slice(1);
@@ -479,7 +483,7 @@ async function searchWithProviders(
479
483
  : await isResolvedProviderAvailable(provider, options),
480
484
  })))).filter((entry) => entry.available).map((entry) => entry.provider);
481
485
  if (providers.length === 0) {
482
- throw new Error("No configured search provider available for provider \"all\". Parallel MCP, DuckDuckGo, Kimi, AnySearch, xAI, Mistral, Bright Data, SerpBase, Serper, Valyu, and XCrawl are excluded.");
486
+ throw new Error("No configured search provider available for provider \"all\". Parallel MCP, DuckDuckGo, Kimi, AnySearch, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Valyu, and XCrawl are excluded.");
483
487
  }
484
488
 
485
489
  const settled = await Promise.allSettled(
@@ -772,7 +776,7 @@ export async function search(query: string, options: FullSearchOptions = {}): Pr
772
776
  " 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TINYFISH_API_KEY, SEARCH1API_KEY, SEARCHINFINITY_API_KEY, QUERIT_API_KEY, TAVILY_API_KEY, FIRECRAWL_BASE_URL, JINA_API_KEY, SERPDIVE_API_KEY, KAGI_API_KEY, BOCHA_API_KEY, OLLAMA_API_KEY, SEARXNG_BASE_URL, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\n" +
773
777
  " 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\n" +
774
778
  " 5. Sign into gemini.google.com in a supported Chromium-based browser\n" +
775
- " 6. Explicitly select provider: \"anysearch\" for anonymous AnySearch, \"xcrawl\" for XCrawl, \"xai\" for Grok, \"mistral\" for Mistral Conversations web search, \"brightdata\" with brightdataSerpZone for paid Bright Data SERP, \"serpbase\" or \"serper\" for Google SERP, or \"valyu\" for research search"
779
+ " 6. Explicitly select provider: \"anysearch\" for anonymous AnySearch, \"xcrawl\" for XCrawl, \"xai\" for Grok, \"mistral\" for Mistral Conversations web search, \"brightdata\" with brightdataSerpZone for paid Bright Data SERP, \"serpbase\", \"serpapi\", or \"serper\" for Google SERP, or \"valyu\" for research search"
776
780
  );
777
781
  }
778
782
 
@@ -2,7 +2,7 @@ import type { AgentToolResult, ExtensionAPI, ExtensionContext } from "@earendil-
2
2
  import { Box, Text, truncateToWidth, type KeyId } from "@earendil-works/pi-tui";
3
3
  import { Type } from "typebox";
4
4
  import pLimit from "p-limit";
5
- import { StringEnum, type ImageContent, type TextContent } from "@earendil-works/pi-ai/compat";
5
+ import type { ImageContent, TextContent } from "@earendil-works/pi-ai/compat";
6
6
  import type { ExtractedContent, ExtractOptions } from "./extract.ts";
7
7
  import { normalizeFetchContentParams } from "./fetch-params.ts";
8
8
  import { resolveAuthFetchProfile, type AuthFetchProfile } from "./auth-fetch.ts";
@@ -69,11 +69,13 @@ import { isMistralAvailable } from "./mistral-search.ts";
69
69
  import { isKimiSearchAvailable } from "./kimi-search.ts";
70
70
  import { isBrightDataAvailable } from "./brightdata.ts";
71
71
  import { isSerpBaseAvailable } from "./serpbase.ts";
72
+ import { isSerpApiAvailable } from "./serpapi.ts";
72
73
  import { isSerperAvailable } from "./serper.ts";
73
74
  import { isValyuAvailable } from "./valyu.ts";
74
75
  import { isXcrawlAvailable } from "./xcrawl.ts";
75
76
  import { buildSearchErrorPlan, type SearchErrorDetails, type SearchErrorPlan } from "./render-search-error.ts";
76
77
  import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix } from "./summary-model-scope.ts";
78
+ import { registerCuratorRunLifecycle, resolveWebSearchWorkflow, type WebSearchWorkflow } from "./curator-run.ts";
77
79
  import {
78
80
  buildResearchArtifact,
79
81
  withClaimAssessment,
@@ -83,6 +85,16 @@ import {
83
85
  type ResearchArtifact,
84
86
  } from "./source-check.ts";
85
87
 
88
+ // Match pi-ai's StringEnum without loading its compat barrel during registration.
89
+ function StringEnum<T extends string[]>(values: T, options?: { description?: string; default?: T[number] }) {
90
+ return Type.Unsafe<T[number]>({
91
+ type: "string",
92
+ enum: values,
93
+ ...(options?.description && { description: options.description }),
94
+ ...(options?.default && { default: options.default }),
95
+ });
96
+ }
97
+
86
98
  type ExtensionTheme = ExtensionContext["ui"]["theme"];
87
99
 
88
100
  const WEB_SEARCH_CONFIG_PATH = getWebSearchConfigPath();
@@ -139,6 +151,7 @@ interface WebSearchConfig {
139
151
  kagiApiKey?: unknown;
140
152
  ollamaApiKey?: unknown;
141
153
  serpbaseApiKey?: unknown;
154
+ serpapiApiKey?: unknown;
142
155
  serperApiKey?: unknown;
143
156
  tinyfishApiKey?: unknown;
144
157
  valyuApiKey?: unknown;
@@ -170,7 +183,6 @@ interface WebSearchConfig {
170
183
  };
171
184
  }
172
185
 
173
- type WebSearchWorkflow = "none" | "summary-review" | "auto-summary";
174
186
  type CuratorWorkflow = "summary-review";
175
187
  export type CuratorProvider = Exclude<SearchProvider, "auto">;
176
188
  type SummaryWorkflow = "summary-review" | "auto-summary";
@@ -347,14 +359,6 @@ function normalizeCuratorTimeoutSeconds(value: unknown): number | undefined {
347
359
  return Math.min(normalized, MAX_CURATOR_TIMEOUT_SECONDS);
348
360
  }
349
361
 
350
- function resolveWorkflow(input: unknown, hasUI: boolean): WebSearchWorkflow {
351
- const normalized = typeof input === "string" ? input.trim().toLowerCase() : "";
352
- if (normalized === "auto-summary") return "auto-summary";
353
- if (!hasUI) return "none";
354
- if (normalized === "none") return "none";
355
- return "summary-review";
356
- }
357
-
358
362
  function normalizeQueryList(queryList: unknown[]): string[] {
359
363
  const normalized: string[] = [];
360
364
  for (const query of queryList) {
@@ -445,6 +449,7 @@ async function getProviderAvailability(ctx: ExtensionContext): Promise<ProviderA
445
449
  mistral: isMistralAvailable(),
446
450
  brightdata: isBrightDataAvailable(),
447
451
  serpbase: isSerpBaseAvailable(),
452
+ serpapi: isSerpApiAvailable(),
448
453
  serper: isSerperAvailable(),
449
454
  valyu: isValyuAvailable(),
450
455
  };
@@ -1058,6 +1063,7 @@ function handleSessionChange(ctx: ExtensionContext): void {
1058
1063
 
1059
1064
  export default function (pi: ExtensionAPI) {
1060
1065
  const initConfig = loadConfigForExtensionInit();
1066
+ const curatorRunState = registerCuratorRunLifecycle(pi);
1061
1067
  installGlobalProxyFetch();
1062
1068
  const toolNames = resolveToolNames(initConfig);
1063
1069
  const webSearchEnabled = isToolEnabled(initConfig, "webSearch");
@@ -1559,6 +1565,7 @@ export default function (pi: ExtensionAPI) {
1559
1565
  },
1560
1566
  onSubmit(payload) {
1561
1567
  if (pendingCurates.get(callId) !== pc) return;
1568
+ if (payload.autoApproveRemainingSearches) curatorRunState.approveRemainingSearches();
1562
1569
  searchAbort.abort();
1563
1570
  const filtered = payload.selectedQueryIndices.length > 0
1564
1571
  ? filterByQueryIndices(payload.selectedQueryIndices, pc.searchResults)
@@ -1783,7 +1790,7 @@ export default function (pi: ExtensionAPI) {
1783
1790
  name: toolNames.webSearch,
1784
1791
  label: "Web Search",
1785
1792
  description:
1786
- `Search the web using OpenAI, Brave, Parallel, Parallel MCP, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, SearXNG, DuckDuckGo, Exa, Perplexity, Gemini, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, or Serper. Pass a provider array to search only those providers simultaneously, or use provider "all" to search every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, and Serper. Returns an AI-synthesized answer with source citations. OpenAI search uses a Codex subscription or OpenAI API key; Kimi search uses a Kimi Code Plan authenticated through /login kimi-coding; xAI search uses a SuperGrok/X Premium subscription or xAI API key; Mistral search uses a Mistral API key. Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, and Serper are available only when explicitly selected. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches auto-open the interactive browser curator and stream results live; set workflow to "none" to skip curation or "auto-summary" for a model-generated summary without the browser curator. The configured provider is used when provider is omitted or set to auto; omit provider unless explicitly overriding it. Without a configured provider, SearXNG is preferred first for local/private search. When the active Pi model is openai-codex, Codex-backed OpenAI search is preferred next. Otherwise Exa is preferred before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Perplexity, Gemini API, or Gemini Web.`,
1793
+ `Search the web using OpenAI, Brave, Parallel, Parallel MCP, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, SearXNG, DuckDuckGo, Exa, Perplexity, Gemini, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, or Serper. Pass a provider array to search only those providers simultaneously, or use provider "all" to search every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, and Serper. Returns an AI-synthesized answer with source citations. OpenAI search uses a Codex subscription or OpenAI API key; Kimi search uses a Kimi Code Plan authenticated through /login kimi-coding; xAI search uses a SuperGrok/X Premium subscription or xAI API key; Mistral search uses a Mistral API key. Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, and Serper are available only when explicitly selected. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches auto-open the interactive browser curator and stream results live; set workflow to "none" to skip curation or "auto-summary" for a model-generated summary without the browser curator. The configured provider is used when provider is omitted or set to auto; omit provider unless explicitly overriding it. Without a configured provider, SearXNG is preferred first for local/private search. When the active Pi model is openai-codex, Codex-backed OpenAI search is preferred next. Otherwise Exa is preferred before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Perplexity, Gemini API, or Gemini Web.`,
1787
1794
  promptSnippet:
1788
1795
  "Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage. Omit provider unless explicitly overriding the configured default.",
1789
1796
  parameters: Type.Object({
@@ -1795,14 +1802,14 @@ export default function (pi: ExtensionAPI) {
1795
1802
  StringEnum(["day", "week", "month", "year"], { description: "Filter by recency" }),
1796
1803
  ),
1797
1804
  domainFilter: Type.Optional(Type.Array(Type.String(), { description: "Limit to domains (prefix with - to exclude)" })),
1798
- provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; use all to search every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, and Serper, omit this field to use the configured provider, or use auto when none is configured")),
1805
+ provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; use all to search every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, and Serper, omit this field to use the configured provider, or use auto when none is configured")),
1799
1806
  workflow: Type.Optional(
1800
1807
  StringEnum(["none", "summary-review", "auto-summary"], {
1801
1808
  description: "Search workflow mode: none = no curator, summary-review = open curator with auto summary draft (default), auto-summary = generate summary without opening curator",
1802
1809
  }),
1803
1810
  ),
1804
1811
  proxy: Type.Optional(Type.String({
1805
- description: "http(s) proxy URL (e.g. http://host:port) used for every outbound request in this call (search APIs and content fetches). Node fetch ignores HTTP(S)_PROXY env vars, so set this (or `proxy` in web-search.json) when direct access is blocked; empty string forces direct access.",
1812
+ description: "http(s) or socks proxy URL (e.g. http://host:port or socks5h://host:port) used for every outbound request in this call (search APIs and content fetches). Node fetch ignores HTTP(S)_PROXY env vars, so set this (or `proxy` in web-search.json) when direct access is blocked; empty string forces direct access.",
1806
1813
  })),
1807
1814
  }),
1808
1815
 
@@ -1813,7 +1820,7 @@ export default function (pi: ExtensionAPI) {
1813
1820
  : (params.query !== undefined ? expandQueryString(params.query) : []);
1814
1821
  const queryList = normalizeQueryList(rawQueryList);
1815
1822
  const configWorkflow = loadConfigForExtensionInit().workflow;
1816
- const workflow = resolveWorkflow(params.workflow ?? configWorkflow, ctx?.hasUI !== false);
1823
+ const workflow = curatorRunState.resolve(params.workflow, configWorkflow, ctx?.hasUI !== false);
1817
1824
  const shouldCurate = workflow === "summary-review";
1818
1825
  const recencyFilter = normalizeRecencyFilter(params.recencyFilter);
1819
1826
 
@@ -2389,9 +2396,9 @@ export default function (pi: ExtensionAPI) {
2389
2396
  fetchContent: Type.Optional(Type.Boolean({ description: "Fetch up to 5 result pages for exact passage extraction." })),
2390
2397
  recencyFilter: Type.Optional(StringEnum(["day", "week", "month", "year"], { description: "Filter by recency." })),
2391
2398
  domainFilter: Type.Optional(Type.Array(Type.String(), { description: "Limit to domains; prefix with - to exclude." })),
2392
- provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; all searches every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, and Serper")),
2399
+ provider: Type.Optional(searchProviderSchema("Search provider or non-empty list of providers to search simultaneously; all searches every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, and Serper")),
2393
2400
  proxy: Type.Optional(Type.String({
2394
- description: "http(s) proxy URL (e.g. http://host:port) used for every outbound request in this call (search APIs and result-page fetches). Empty string forces direct access.",
2401
+ description: "http(s) or socks proxy URL (e.g. http://host:port or socks5h://host:port) used for every outbound request in this call (search APIs and result-page fetches). Empty string forces direct access.",
2395
2402
  })),
2396
2403
  }),
2397
2404
  async execute(_callId, params, signal, _onUpdate, ctx) {
@@ -2512,7 +2519,7 @@ export default function (pi: ExtensionAPI) {
2512
2519
  description: "Opt into an authFetch profile for local browser-cookie fetching. Use a profile name, or true only when exactly one profile exists.",
2513
2520
  })),
2514
2521
  proxy: Type.Optional(Type.String({
2515
- description: "http(s) proxy URL (e.g. http://host:port) used for this fetch. Needed when the target is unreachable directly; localhost and NO_PROXY hosts always bypass the proxy. Empty string forces direct access.",
2522
+ description: "http(s) or socks proxy URL (e.g. http://host:port or socks5h://host:port) used for this fetch. Needed when the target is unreachable directly; localhost and NO_PROXY hosts always bypass the proxy. Empty string forces direct access.",
2516
2523
  })),
2517
2524
  }),
2518
2525
 
@@ -3424,7 +3431,7 @@ export default function (pi: ExtensionAPI) {
3424
3431
 
3425
3432
  let newWorkflow: WebSearchWorkflow;
3426
3433
  if (arg.length === 0) {
3427
- const current = resolveWorkflow(loadConfigForExtensionInit().workflow, true);
3434
+ const current = resolveWebSearchWorkflow(loadConfigForExtensionInit().workflow, true);
3428
3435
  newWorkflow = current === "none" ? "summary-review" : "none";
3429
3436
  } else if (arg === "on") {
3430
3437
  newWorkflow = "summary-review";