@herbertgao/pi-extensions 2026.8.5 → 2026.8.7
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.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +119 -103
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +37 -0
- package/node_modules/pi-web-access/README.md +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +51 -23
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -12,11 +12,73 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Refs: #130, #131, #132
|
|
14
14
|
*/
|
|
15
|
+
import * as path from "node:path";
|
|
15
16
|
import { createSubsystemLogger } from "./extension-log.js";
|
|
16
17
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
18
|
+
import { classifyProbeFailure, createAvailabilityLatch, describeInstallAttempt, describeProbeEvidence, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
17
19
|
export class SecurityScanClient {
|
|
18
20
|
toolName;
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Availability memo, backed by the shared transient-aware latch (#1467).
|
|
23
|
+
*
|
|
24
|
+
* Assigning `false` still means "durable: this machine does not have the
|
|
25
|
+
* tool" — every existing subclass write keeps its meaning. A transient
|
|
26
|
+
* failure must expire instead: `probeVersion` latches its own transient
|
|
27
|
+
* verdicts (#1501), and any other transient failure (a scan or install
|
|
28
|
+
* timeout) goes through `markTransientlyUnavailable`, so the tool can come
|
|
29
|
+
* back without a host restart.
|
|
30
|
+
*/
|
|
31
|
+
availabilityLatch = createAvailabilityLatch();
|
|
32
|
+
get available() {
|
|
33
|
+
return this.availabilityLatch.read();
|
|
34
|
+
}
|
|
35
|
+
set available(value) {
|
|
36
|
+
if (value === true) {
|
|
37
|
+
this.availabilityLatch.noteAvailable();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (value !== false) {
|
|
41
|
+
this.availabilityLatch.reset();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.availabilityLatch.noteUnavailable("missing", "not-found");
|
|
45
|
+
this.logDurableAbsence();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Latch a durable absence WITH the facts behind it. Prefer this over
|
|
49
|
+
* `available = false` wherever the call site knows what actually failed — a
|
|
50
|
+
* `missing` row carrying `install: "failed"` is a retry candidate, one
|
|
51
|
+
* carrying `install: "not-attempted"` is a policy decision, and a bare one is
|
|
52
|
+
* a plain absence. Only the record can tell them apart.
|
|
53
|
+
*/
|
|
54
|
+
noteDurableAbsence(evidence, options = {}) {
|
|
55
|
+
this.availabilityLatch.noteUnavailable("missing", "not-found");
|
|
56
|
+
this.logDurableAbsence(evidence, options.elapsedMs);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* One record per durable-absence ASSERTION, whether or not the call site had
|
|
60
|
+
* evidence. Before this, `available = false` after a failed install was a
|
|
61
|
+
* silent latch: the tool went quiet for the session with nothing in
|
|
62
|
+
* latency.log to audit (#1500). Bounded — the verdict latches, so at most one
|
|
63
|
+
* row per client per session.
|
|
64
|
+
*
|
|
65
|
+
* `elapsedMs` is whatever the caller measured (an install attempt's duration);
|
|
66
|
+
* there is no probe here, so it is 0 unless the caller has something real.
|
|
67
|
+
*/
|
|
68
|
+
logDurableAbsence(evidence, elapsedMs = 0) {
|
|
69
|
+
logAvailabilityDecision({
|
|
70
|
+
tool: this.toolName,
|
|
71
|
+
verdict: "unavailable",
|
|
72
|
+
outcome: "missing",
|
|
73
|
+
cause: "not-found",
|
|
74
|
+
elapsedMs,
|
|
75
|
+
latched: true,
|
|
76
|
+
classifiedBy: "caller",
|
|
77
|
+
...(evidence !== undefined && { evidence }),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Outcome of the most recent `probeVersion` call. */
|
|
81
|
+
lastProbeOutcome = null;
|
|
20
82
|
ensureInFlight = null;
|
|
21
83
|
inFlight = new Map();
|
|
22
84
|
binaryPath = null;
|
|
@@ -51,18 +113,128 @@ export class SecurityScanClient {
|
|
|
51
113
|
}
|
|
52
114
|
/**
|
|
53
115
|
* Spawn `toolName <versionArgs>` and report whether it answered cleanly.
|
|
54
|
-
*
|
|
116
|
+
* Classifies the failure (`lastProbeOutcome`) and emits one
|
|
117
|
+
* availability-decision record, so a probe that keeps timing out is visible
|
|
118
|
+
* in latency.log rather than inferred from silence (#1467).
|
|
119
|
+
*
|
|
120
|
+
* Latch ownership is split by outcome: success and durable failures leave
|
|
121
|
+
* `this.available` to the caller (a missing binary may still be
|
|
122
|
+
* auto-installed), while a TRANSIENT failure is latched by the seam itself
|
|
123
|
+
* with its cooldown — the retry schedule only exists once the latch owns
|
|
124
|
+
* the verdict, and the record is incomplete without it (#1501). Callers
|
|
125
|
+
* must not re-mark a probe transient (a second noteUnavailable would
|
|
126
|
+
* double-escalate the cooldown).
|
|
55
127
|
*/
|
|
56
128
|
async probeVersion(versionArgs) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
129
|
+
const sampler = startHostStallSampler();
|
|
130
|
+
const startedAt = Date.now();
|
|
131
|
+
let probe;
|
|
132
|
+
let hostStallMs;
|
|
133
|
+
try {
|
|
134
|
+
probe = await safeSpawnAsync(this.toolName, versionArgs, {
|
|
135
|
+
timeout: 5000,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
hostStallMs = sampler.stop();
|
|
140
|
+
}
|
|
141
|
+
const elapsedMs = Date.now() - startedAt;
|
|
60
142
|
if (!probe.error && probe.status === 0) {
|
|
143
|
+
this.lastProbeOutcome = "success";
|
|
61
144
|
this.log(`${this.toolName} found: ${probe.stdout.trim().split("\n")[0]}`);
|
|
145
|
+
logAvailabilityDecision({
|
|
146
|
+
tool: this.toolName,
|
|
147
|
+
verdict: "available",
|
|
148
|
+
outcome: "success",
|
|
149
|
+
cause: "ok",
|
|
150
|
+
elapsedMs,
|
|
151
|
+
latched: true,
|
|
152
|
+
hostStallMs,
|
|
153
|
+
budgetMs: 5000,
|
|
154
|
+
classifiedBy: "probe",
|
|
155
|
+
evidence: describeProbeEvidence(probe),
|
|
156
|
+
});
|
|
62
157
|
return true;
|
|
63
158
|
}
|
|
159
|
+
const { outcome, cause, evidence } = classifyProbeFailure(probe, {
|
|
160
|
+
hostStallMs,
|
|
161
|
+
});
|
|
162
|
+
this.lastProbeOutcome = outcome;
|
|
163
|
+
// A transient verdict's record is incomplete without the retry schedule,
|
|
164
|
+
// and the schedule only exists once the latch owns the verdict — so the
|
|
165
|
+
// seam writes the expiring verdict itself, at log time (#1501). Durable
|
|
166
|
+
// outcomes stay caller-owned (a missing binary may still be installed).
|
|
167
|
+
const retryAfterMs = outcome === "transient"
|
|
168
|
+
? this.availabilityLatch.noteUnavailable("transient", cause)
|
|
169
|
+
: 0;
|
|
170
|
+
logAvailabilityDecision({
|
|
171
|
+
tool: this.toolName,
|
|
172
|
+
verdict: "unavailable",
|
|
173
|
+
outcome,
|
|
174
|
+
cause,
|
|
175
|
+
elapsedMs,
|
|
176
|
+
latched: outcome !== "transient",
|
|
177
|
+
hostStallMs,
|
|
178
|
+
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
179
|
+
budgetMs: 5000,
|
|
180
|
+
classifiedBy: "probe",
|
|
181
|
+
evidence,
|
|
182
|
+
});
|
|
64
183
|
return false;
|
|
65
184
|
}
|
|
185
|
+
/** True when the last probe failed for a reason that is not the tool's fault. */
|
|
186
|
+
probeWasTransient() {
|
|
187
|
+
return this.lastProbeOutcome === "transient";
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Record a non-durable unavailability: the verdict expires after a cooldown
|
|
191
|
+
* and the next `ensureAvailable` re-probes.
|
|
192
|
+
*
|
|
193
|
+
* Returns that cooldown in ms so the caller can put it in its decision
|
|
194
|
+
* record. A latch you can read in `latency.log` without the retry schedule
|
|
195
|
+
* beside it only tells you the tool is off, not when it comes back.
|
|
196
|
+
*
|
|
197
|
+
* For NON-probe transient failures only (scan/install timeouts):
|
|
198
|
+
* `probeVersion` latches and logs its own transient verdicts (#1501), so
|
|
199
|
+
* re-marking one here would double-escalate the cooldown.
|
|
200
|
+
*
|
|
201
|
+
* Pass `opts.operationClass: "install"` when the failed operation was a
|
|
202
|
+
* network install/compile rather than a cheap probe (#1497): the retry
|
|
203
|
+
* escalates on the install-class schedule, on its OWN cooldown slot (a
|
|
204
|
+
* cheap probe failure can never shorten it), and latches for the session at
|
|
205
|
+
* the attempt ceiling, in which case the return is 0 (latched).
|
|
206
|
+
*/
|
|
207
|
+
markTransientlyUnavailable(cause = "probe-timeout", opts) {
|
|
208
|
+
return this.availabilityLatch.noteUnavailable("transient", cause, opts);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The cause the LATCH settled on, which is not always the cause the caller
|
|
212
|
+
* passed in: at the install-class ceiling the latch rewrites it to
|
|
213
|
+
* `install-retry-exhausted` (#1497). A decision record built from the
|
|
214
|
+
* caller's own cause would say `probe-timeout` for a verdict that is no
|
|
215
|
+
* longer being retried, so the record reads it back from here.
|
|
216
|
+
*/
|
|
217
|
+
latchedCause() {
|
|
218
|
+
return this.availabilityLatch.getCause();
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* #1623: public window onto the availability verdict for callers OUTSIDE
|
|
222
|
+
* the dispatch graph (mode=full's fresh-fetch) that need to say WHY
|
|
223
|
+
* `ensureAvailable()` most recently returned false — using the SAME
|
|
224
|
+
* `AvailabilityOutcome`/`AvailabilityCause` taxonomy every dispatch-side
|
|
225
|
+
* message is built from, rather than a re-guessed "binary unavailable"
|
|
226
|
+
* that can't tell a transient retry-cooldown probe from a durable absence.
|
|
227
|
+
* `latchedCause()`/`lastProbeOutcome` above stay `protected` for
|
|
228
|
+
* dispatch-internal callers; this is the one public seam for everyone
|
|
229
|
+
* else.
|
|
230
|
+
*/
|
|
231
|
+
getAvailabilityVerdict() {
|
|
232
|
+
return {
|
|
233
|
+
outcome: this.availabilityLatch.getOutcome(),
|
|
234
|
+
cause: this.availabilityLatch.getCause(),
|
|
235
|
+
retryAtMs: this.availabilityLatch.getRetryAtMs(),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
66
238
|
/**
|
|
67
239
|
* Standard availability path for the GitHub-release tools (gitleaks, trivy):
|
|
68
240
|
* PATH probe first, then fall back to the pi-lens installer's `ensureTool`.
|
|
@@ -73,17 +245,61 @@ export class SecurityScanClient {
|
|
|
73
245
|
this.available = true;
|
|
74
246
|
return true;
|
|
75
247
|
}
|
|
248
|
+
if (this.probeWasTransient()) {
|
|
249
|
+
// A timed-out probe is not evidence the tool is absent, so it must not
|
|
250
|
+
// trigger an install NOR latch a permanent `false`. probeVersion has
|
|
251
|
+
// already recorded the expiring verdict and its retry schedule
|
|
252
|
+
// (#1501) — re-marking it here would double-escalate the cooldown.
|
|
253
|
+
this.log(`${this.toolName} availability probe timed out; not installing, will retry`);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
76
256
|
this.log(`${this.toolName} not found, attempting auto-install`);
|
|
77
|
-
const { ensureTool } = await import("./installer/index.js");
|
|
257
|
+
const { ensureTool, getInstallAttempt } = await import("./installer/index.js");
|
|
258
|
+
const installStartedAt = Date.now();
|
|
78
259
|
const installed = await ensureTool(this.toolName);
|
|
79
260
|
if (!installed) {
|
|
80
|
-
this.
|
|
81
|
-
|
|
261
|
+
// #1500: this classification is ASSERTED, not derived. It is justified —
|
|
262
|
+
// the probe already classified a genuine absence, and a failed repair
|
|
263
|
+
// leaves the tool absent — but the install failure has no taxonomy of
|
|
264
|
+
// its own, so a network blip and a permanently unavailable release look
|
|
265
|
+
// identical from here. Record what happened rather than only the verdict.
|
|
266
|
+
//
|
|
267
|
+
// `ensureTool` answers the same empty result whether it TRIED and failed
|
|
268
|
+
// or declined to try (auto-install off, project trust denied), so the
|
|
269
|
+
// attempt is read from the reason it records rather than assumed — the
|
|
270
|
+
// review found `install: "failed"` being written for attempts that never
|
|
271
|
+
// happened.
|
|
272
|
+
const attempt = getInstallAttempt(this.toolName);
|
|
273
|
+
this.log(attempt?.outcome === "failed"
|
|
274
|
+
? `${this.toolName} auto-install failed: ${attempt.reason ?? "unknown reason"}`
|
|
275
|
+
: `${this.toolName} auto-install did not run: ${attempt?.reason ?? "no attempt recorded"}`);
|
|
276
|
+
this.noteDurableAbsence(describeInstallAttempt(attempt), {
|
|
277
|
+
elapsedMs: Date.now() - installStartedAt,
|
|
278
|
+
});
|
|
82
279
|
return false;
|
|
83
280
|
}
|
|
84
281
|
this.binaryPath = installed;
|
|
85
282
|
this.available = true;
|
|
86
283
|
this.log(`${this.toolName} auto-installed at ${installed}`);
|
|
284
|
+
// The probe already wrote a latched `unavailable` row (#1500's durable
|
|
285
|
+
// assertion doesn't fire here — install SUCCEEDED — but probeVersion's
|
|
286
|
+
// verdict still did, before this call ever ran). Without a compensating
|
|
287
|
+
// row, the durable record says the lane is off when it just came back
|
|
288
|
+
// on: the exact defect #1606 was filed over.
|
|
289
|
+
logAvailabilityDecision({
|
|
290
|
+
tool: this.toolName,
|
|
291
|
+
verdict: "available",
|
|
292
|
+
outcome: "success",
|
|
293
|
+
cause: "ok",
|
|
294
|
+
elapsedMs: Date.now() - installStartedAt,
|
|
295
|
+
latched: true,
|
|
296
|
+
classifiedBy: "caller",
|
|
297
|
+
evidence: {
|
|
298
|
+
install: "succeeded",
|
|
299
|
+
binary: path.basename(installed),
|
|
300
|
+
source: "managed-dir",
|
|
301
|
+
},
|
|
302
|
+
});
|
|
87
303
|
return true;
|
|
88
304
|
}
|
|
89
305
|
/**
|
|
@@ -12,6 +12,8 @@ import { getSgCommand, resolveManagedToolClient, } from "./dispatch/runners/util
|
|
|
12
12
|
import { getProjectIgnoreGlobs } from "./file-utils.js";
|
|
13
13
|
import { findGlobalBinary } from "./package-manager.js";
|
|
14
14
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
15
|
+
import { createSingleFlight } from "./single-flight.js";
|
|
16
|
+
import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
15
17
|
/**
|
|
16
18
|
* Build the `bash -c` argv that runs `cmd` with `allArgs` as POSITIONAL
|
|
17
19
|
* parameters. The script is the constant `"$0" "$@"`, so bash re-emits the
|
|
@@ -33,6 +35,8 @@ function sgExcludeArgsForProject(rootDir) {
|
|
|
33
35
|
]);
|
|
34
36
|
}
|
|
35
37
|
const DEFAULT_EXEC_TIMEOUT_MS = 30_000;
|
|
38
|
+
/** Budget for a single `--version` candidate probe, ms. */
|
|
39
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
36
40
|
const MAX_SG_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
37
41
|
/**
|
|
38
42
|
* Format metavariable captures for display below a match line.
|
|
@@ -94,8 +98,58 @@ export class SgRunner {
|
|
|
94
98
|
log;
|
|
95
99
|
sgPath = null;
|
|
96
100
|
sgArgsPrefix = [];
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Availability memo, backed by the shared transient-aware latch (#1476).
|
|
103
|
+
*
|
|
104
|
+
* Before this, one failed sweep over every candidate — timeouts included —
|
|
105
|
+
* latched `false` for the life of the process AND ran a full auto-install
|
|
106
|
+
* first. A host stall during warm-up therefore both disabled ast-grep until
|
|
107
|
+
* restart and paid for an install nobody needed.
|
|
108
|
+
*/
|
|
109
|
+
availabilityLatch = createAvailabilityLatch();
|
|
110
|
+
/**
|
|
111
|
+
* At-most-one sweep in flight, via the shared primitive (#1753). One
|
|
112
|
+
* instance owns one question, so the key is a constant.
|
|
113
|
+
*/
|
|
114
|
+
ensureFlight = createSingleFlight();
|
|
115
|
+
/**
|
|
116
|
+
* Whether a DIRECT candidate — one that would have been ast-grep itself —
|
|
117
|
+
* failed for a transient reason in the current sweep. Only these block the
|
|
118
|
+
* install: a slow `npx` says nothing about whether ast-grep is on this
|
|
119
|
+
* machine, and letting it block turned "ast-grep is genuinely absent on a
|
|
120
|
+
* slow host" into "ast-grep is never installed and npx is re-spawned every
|
|
121
|
+
* sweep, forever".
|
|
122
|
+
*/
|
|
123
|
+
sweepSawTransient = false;
|
|
124
|
+
sweepTransientCause = "probe-timeout";
|
|
125
|
+
/**
|
|
126
|
+
* The DIRECT candidates that were unreachable, in ask order (#1568).
|
|
127
|
+
*
|
|
128
|
+
* The sweep returns at the first candidate that answers, so at the moment of
|
|
129
|
+
* a win this is exactly the set of preferred tiers the winner did not beat on
|
|
130
|
+
* the merits. Fallbacks are excluded on purpose: `npx` is asked before the
|
|
131
|
+
* global-bin and platform-package tiers, and a slow `npx` says nothing about
|
|
132
|
+
* a winner that is a real binary.
|
|
133
|
+
*
|
|
134
|
+
* Basenames, because a candidate can be an absolute global-bin or
|
|
135
|
+
* platform-package path and this list is written to latency.log (#1568
|
|
136
|
+
* review F3).
|
|
137
|
+
*/
|
|
138
|
+
sweepUnreachable = [];
|
|
139
|
+
/** A transient on the npx fallback: not evidence, but not nothing either. */
|
|
140
|
+
sweepFallbackTransient = false;
|
|
141
|
+
sweepFallbackCause = "probe-timeout";
|
|
142
|
+
/** Host stall summed over every probe of the current sweep, ms. */
|
|
143
|
+
sweepHostStallMs = 0;
|
|
144
|
+
/**
|
|
145
|
+
* Candidates — direct or fallback — that this sweep probed and found
|
|
146
|
+
* DURABLY missing (real ENOENT/non-installable, not a stall) (#1593). The
|
|
147
|
+
* retained-arm fallback below only sees whether SOME candidate stalled; this
|
|
148
|
+
* list lets it also check whether the memoized winner itself is one of the
|
|
149
|
+
* candidates this very sweep just disproved, rather than re-serving a
|
|
150
|
+
* command that ENOENTed a moment ago because an unrelated sibling stalled.
|
|
151
|
+
*/
|
|
152
|
+
sweepDurablyMissing = [];
|
|
99
153
|
constructor(verbose = false) {
|
|
100
154
|
this.log = verbose
|
|
101
155
|
? createSubsystemLogger("sg-runner")
|
|
@@ -104,38 +158,44 @@ export class SgRunner {
|
|
|
104
158
|
/**
|
|
105
159
|
* Check if ast-grep CLI is available, auto-install if not.
|
|
106
160
|
*
|
|
107
|
-
* Re-entrancy safe: concurrent first-time callers share
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
161
|
+
* Re-entrancy safe: concurrent first-time callers share one flight, so
|
|
162
|
+
* probing/auto-install isn't duplicated across session-start tasks. The
|
|
163
|
+
* share and the clear-in-finally belong to `singleFlight` (#1753); this
|
|
164
|
+
* method owns only the latch short-circuit above it. The sweep-local
|
|
165
|
+
* bookkeeping `doEnsureAvailable` resets on entry is untouched — it is
|
|
166
|
+
* per-sweep state, not concurrency state.
|
|
111
167
|
*/
|
|
112
168
|
async ensureAvailable() {
|
|
113
|
-
// Fast path: already
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
return
|
|
118
|
-
this.
|
|
119
|
-
try {
|
|
120
|
-
return await this.ensureInFlight;
|
|
121
|
-
}
|
|
122
|
-
finally {
|
|
123
|
-
this.ensureInFlight = null;
|
|
124
|
-
}
|
|
169
|
+
// Fast path: already decided. `read()` returns null when the last verdict
|
|
170
|
+
// was transient and its cooldown expired, which re-enters the sweep.
|
|
171
|
+
const memo = this.availabilityLatch.read();
|
|
172
|
+
if (memo !== null)
|
|
173
|
+
return memo;
|
|
174
|
+
return this.ensureFlight.run("ast-grep", () => this.doEnsureAvailable());
|
|
125
175
|
}
|
|
126
176
|
async doEnsureAvailable() {
|
|
177
|
+
const startedAt = Date.now();
|
|
178
|
+
this.sweepSawTransient = false;
|
|
179
|
+
this.sweepTransientCause = "probe-timeout";
|
|
180
|
+
this.sweepUnreachable = [];
|
|
181
|
+
this.sweepFallbackTransient = false;
|
|
182
|
+
this.sweepFallbackCause = "probe-timeout";
|
|
183
|
+
this.sweepHostStallMs = 0;
|
|
184
|
+
this.sweepDurablyMissing = [];
|
|
127
185
|
// Step 1: PATH — canonical binary names + npx fallback.
|
|
128
186
|
// Prefer ast-grep over sg on Linux: /usr/bin/sg is util-linux, not ast-grep.
|
|
129
187
|
const pathCommand = await this.probeCommandCandidates([
|
|
130
188
|
{ cmd: "ast-grep", argsPrefix: [] },
|
|
131
189
|
{ cmd: "sg", argsPrefix: [] },
|
|
132
|
-
|
|
190
|
+
// `npx --no -- ast-grep` starts a Node process before it can answer, so
|
|
191
|
+
// it is the candidate most likely to blow a 5 s budget on a cold or busy
|
|
192
|
+
// box. Marked a fallback so its timeout cannot veto the install.
|
|
193
|
+
{ cmd: "npx", argsPrefix: ["--no", "--", "ast-grep"], fallback: true },
|
|
133
194
|
]);
|
|
134
195
|
if (pathCommand) {
|
|
135
196
|
this.sgPath = pathCommand.cmd;
|
|
136
197
|
this.sgArgsPrefix = pathCommand.argsPrefix;
|
|
137
|
-
this.
|
|
138
|
-
this.log(`ast-grep found on PATH: ${pathCommand.cmd}`);
|
|
198
|
+
this.noteAvailable(startedAt, `ast-grep found on PATH: ${pathCommand.cmd}`);
|
|
139
199
|
return true;
|
|
140
200
|
}
|
|
141
201
|
// Step 1b: any package manager's global bin dir (npm/pnpm/yarn/bun).
|
|
@@ -147,8 +207,7 @@ export class SgRunner {
|
|
|
147
207
|
if (globalBin && (await this.probeCommand(globalBin, []))) {
|
|
148
208
|
this.sgPath = globalBin;
|
|
149
209
|
this.sgArgsPrefix = [];
|
|
150
|
-
this.
|
|
151
|
-
this.log(`ast-grep found in global bin: ${globalBin}`);
|
|
210
|
+
this.noteAvailable(startedAt, `ast-grep found in global bin: ${globalBin}`);
|
|
152
211
|
return true;
|
|
153
212
|
}
|
|
154
213
|
}
|
|
@@ -159,8 +218,7 @@ export class SgRunner {
|
|
|
159
218
|
if (platformBinary) {
|
|
160
219
|
this.sgPath = platformBinary;
|
|
161
220
|
this.sgArgsPrefix = [];
|
|
162
|
-
this.
|
|
163
|
-
this.log(`ast-grep found via platform package: ${platformBinary}`);
|
|
221
|
+
this.noteAvailable(startedAt, `ast-grep found via platform package: ${platformBinary}`);
|
|
164
222
|
return true;
|
|
165
223
|
}
|
|
166
224
|
// Step 3: Homebrew (macOS only).
|
|
@@ -169,10 +227,36 @@ export class SgRunner {
|
|
|
169
227
|
if (brewBinary) {
|
|
170
228
|
this.sgPath = brewBinary;
|
|
171
229
|
this.sgArgsPrefix = [];
|
|
172
|
-
this.
|
|
173
|
-
|
|
230
|
+
this.noteAvailable(startedAt, `ast-grep found via Homebrew: ${brewBinary}`);
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// A timeout on a DIRECT candidate means the machine answered, not the tool
|
|
235
|
+
// (#1476). Installing ast-grep because the host event loop stalled is a
|
|
236
|
+
// heavyweight reaction to a hiccup, and latching the result disabled the
|
|
237
|
+
// tool until restart. Retry the sweep later instead.
|
|
238
|
+
//
|
|
239
|
+
// The npx fallback is deliberately NOT part of this test. Gating the
|
|
240
|
+
// install on it regressed the very host this change targets: a slow box
|
|
241
|
+
// with no ast-grep timed out `npx --no -- ast-grep` every sweep, so the
|
|
242
|
+
// install below was never reached and the slow npx was re-spawned on each
|
|
243
|
+
// escalating retry instead — worse than the latch it replaced.
|
|
244
|
+
if (this.sweepSawTransient) {
|
|
245
|
+
// Nothing answered, transiently, while we are still holding a command a
|
|
246
|
+
// previous provisional sweep proved working. Discarding it here would
|
|
247
|
+
// run #1476 backwards — a timeout erasing a positive result — and it
|
|
248
|
+
// would send the sweep on to Step 4 to install a tool that is already
|
|
249
|
+
// runnable. Keep the winner and re-arm the cooldown (#1568 review F1).
|
|
250
|
+
// #1593: a sibling tier stalling this sweep is not license to re-serve
|
|
251
|
+
// a winner that THIS SAME sweep just proved durably missing.
|
|
252
|
+
if (this.availabilityLatch.isProvisional() &&
|
|
253
|
+
this.sgPath &&
|
|
254
|
+
!this.sweepDurablyMissing.includes(this.sgPath)) {
|
|
255
|
+
this.noteAvailable(startedAt, `ast-grep re-probe stalled; keeping ${this.sgPath}`, { retained: true });
|
|
174
256
|
return true;
|
|
175
257
|
}
|
|
258
|
+
this.log("ast-grep availability probe timed out; will retry (not installing)");
|
|
259
|
+
return this.noteUnavailable(startedAt, "transient", this.sweepTransientCause);
|
|
176
260
|
}
|
|
177
261
|
// Step 4: install via the typed shared seam, then validate the returned
|
|
178
262
|
// absolute binary before publishing it.
|
|
@@ -185,11 +269,101 @@ export class SgRunner {
|
|
|
185
269
|
if (installed.outcome === "success") {
|
|
186
270
|
this.sgPath = installed.value;
|
|
187
271
|
this.sgArgsPrefix = [];
|
|
188
|
-
this.
|
|
189
|
-
this.log(`ast-grep auto-installed: ${installed.value}`);
|
|
272
|
+
this.noteAvailable(startedAt, `ast-grep auto-installed: ${installed.value}`);
|
|
190
273
|
return true;
|
|
191
274
|
}
|
|
192
|
-
|
|
275
|
+
// The install failed AND the npx fallback never got a fair hearing, so
|
|
276
|
+
// "ast-grep is not installed" is not a fact yet. Expire the verdict.
|
|
277
|
+
if (this.sweepFallbackTransient) {
|
|
278
|
+
return this.noteUnavailable(startedAt, "transient", this.sweepFallbackCause);
|
|
279
|
+
}
|
|
280
|
+
// #1500: ASSERTED, not derived — and justified, because every candidate
|
|
281
|
+
// probe answered "not found", so the absence is real. What the install did
|
|
282
|
+
// is recorded as evidence rather than folded into the verdict: it is the one
|
|
283
|
+
// fact separating "never installable here" from "the download failed", and
|
|
284
|
+
// from "no install was attempted at all" (auto-install off, trust denied,
|
|
285
|
+
// or an attempt already suppressed this session).
|
|
286
|
+
const { getInstallAttempt } = await import("./installer/index.js");
|
|
287
|
+
return this.noteUnavailable(startedAt, "missing", "not-found", describeInstallAttempt(getInstallAttempt("ast-grep"), {
|
|
288
|
+
installedButRejected: installed.outcome === "non-installable",
|
|
289
|
+
}), "caller");
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Record a successful sweep, with one decision record.
|
|
293
|
+
*
|
|
294
|
+
* The win is latched for the session UNLESS a direct candidate ahead of it
|
|
295
|
+
* was unreachable (#1568). `sweepSawTransient` is set only by candidates the
|
|
296
|
+
* sweep already asked, and the sweep returns at the first one that answers,
|
|
297
|
+
* so at this point the flag means "a tier the winner is supposed to lose to
|
|
298
|
+
* never got a fair hearing". Latching that pinned the session to
|
|
299
|
+
* `npx --no -- ast-grep` — a Node start per invocation — over a healthy
|
|
300
|
+
* ast-grep on PATH, until the next restart. Provisional instead: served now,
|
|
301
|
+
* re-swept once the stalled tier's cooldown expires.
|
|
302
|
+
*
|
|
303
|
+
* The install arm cannot reach here provisionally: `doEnsureAvailable`
|
|
304
|
+
* returns before Step 4 whenever `sweepSawTransient` is set.
|
|
305
|
+
*
|
|
306
|
+
* `retained` marks the other provisional case (#1568 review F1): no candidate
|
|
307
|
+
* answered at all, and the winner being reported is the one the previous
|
|
308
|
+
* sweep found, kept rather than discarded on a timeout.
|
|
309
|
+
*/
|
|
310
|
+
noteAvailable(startedAt, message, opts = {}) {
|
|
311
|
+
const provisional = this.sweepSawTransient;
|
|
312
|
+
let retryAfterMs = 0;
|
|
313
|
+
if (provisional) {
|
|
314
|
+
retryAfterMs = this.availabilityLatch.noteProvisionallyAvailable(this.sweepTransientCause);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
this.availabilityLatch.noteAvailable();
|
|
318
|
+
}
|
|
319
|
+
this.log(message);
|
|
320
|
+
logAvailabilityDecision({
|
|
321
|
+
tool: "ast-grep",
|
|
322
|
+
verdict: "available",
|
|
323
|
+
outcome: "success",
|
|
324
|
+
cause: provisional ? this.sweepTransientCause : "ok",
|
|
325
|
+
elapsedMs: Date.now() - startedAt,
|
|
326
|
+
latched: !provisional,
|
|
327
|
+
hostStallMs: this.sweepHostStallMs,
|
|
328
|
+
budgetMs: PROBE_TIMEOUT_MS,
|
|
329
|
+
...(provisional && {
|
|
330
|
+
provisional: true,
|
|
331
|
+
unreachablePreferred: [...this.sweepUnreachable],
|
|
332
|
+
...(opts.retained === true && { retained: true }),
|
|
333
|
+
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
334
|
+
}),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Record a failed sweep. A `transient` outcome expires after a bounded
|
|
339
|
+
* cooldown; anything else is remembered for the session, as before. The
|
|
340
|
+
* elapsed/stall numbers describe the WHOLE sweep, because the verdict is
|
|
341
|
+
* about the sweep rather than any single candidate — reporting zero here
|
|
342
|
+
* would erase the evidence that cracked #1467.
|
|
343
|
+
*/
|
|
344
|
+
noteUnavailable(startedAt, outcome, cause, evidence,
|
|
345
|
+
/**
|
|
346
|
+
* How this arm reached its verdict. Per arm on purpose (#1500 review): the
|
|
347
|
+
* sweep's own transient/missing conclusions ARE derived from candidate
|
|
348
|
+
* probes, but the post-install assertion is not, and hardcoding `"probe"`
|
|
349
|
+
* here labelled an assertion as a derivation — the exact confusion the
|
|
350
|
+
* field was added to remove.
|
|
351
|
+
*/
|
|
352
|
+
classifiedBy = "probe") {
|
|
353
|
+
const retryAfterMs = this.availabilityLatch.noteUnavailable(outcome, cause);
|
|
354
|
+
logAvailabilityDecision({
|
|
355
|
+
tool: "ast-grep",
|
|
356
|
+
verdict: "unavailable",
|
|
357
|
+
outcome,
|
|
358
|
+
cause,
|
|
359
|
+
elapsedMs: Date.now() - startedAt,
|
|
360
|
+
latched: outcome !== "transient",
|
|
361
|
+
hostStallMs: this.sweepHostStallMs,
|
|
362
|
+
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
363
|
+
budgetMs: PROBE_TIMEOUT_MS,
|
|
364
|
+
classifiedBy,
|
|
365
|
+
...(evidence !== undefined && { evidence }),
|
|
366
|
+
});
|
|
193
367
|
return false;
|
|
194
368
|
}
|
|
195
369
|
/**
|
|
@@ -270,17 +444,55 @@ export class SgRunner {
|
|
|
270
444
|
isAstGrepVersionOutput(output) {
|
|
271
445
|
return /\bast[- ]grep\b/i.test(output);
|
|
272
446
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Probe one candidate. A failure is CLASSIFIED, not merely counted: a
|
|
449
|
+
* timeout/abort marks the whole sweep transient so the caller retries later
|
|
450
|
+
* instead of installing and latching. A command that answers but is not
|
|
451
|
+
* ast-grep (Linux `/usr/bin/sg` is util-linux) is a durable no.
|
|
452
|
+
*/
|
|
453
|
+
async probeCommand(cmd, argsPrefix, fallback = false) {
|
|
454
|
+
// Host-side budget: measure the loop stall that overlapped the window so
|
|
455
|
+
// the classifier can tell "the tool is slow" from "the host was busy".
|
|
456
|
+
const sampler = startHostStallSampler();
|
|
457
|
+
let result;
|
|
458
|
+
let hostStallMs;
|
|
459
|
+
try {
|
|
460
|
+
result = await safeSpawnAsync(cmd, [...argsPrefix, "--version"], {
|
|
461
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
finally {
|
|
465
|
+
hostStallMs = sampler.stop();
|
|
466
|
+
this.sweepHostStallMs += hostStallMs;
|
|
467
|
+
}
|
|
468
|
+
if (!result.error &&
|
|
278
469
|
result.status === 0 &&
|
|
279
|
-
this.isAstGrepVersionOutput(`${result.stdout}\n${result.stderr}`))
|
|
470
|
+
this.isAstGrepVersionOutput(`${result.stdout}\n${result.stderr}`)) {
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
const { outcome, cause } = classifyProbeFailure(result, { hostStallMs });
|
|
474
|
+
if (outcome === "transient") {
|
|
475
|
+
if (fallback) {
|
|
476
|
+
this.sweepFallbackTransient = true;
|
|
477
|
+
this.sweepFallbackCause = cause;
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
this.sweepSawTransient = true;
|
|
481
|
+
this.sweepTransientCause = cause;
|
|
482
|
+
const name = path.basename(cmd);
|
|
483
|
+
if (!this.sweepUnreachable.includes(name)) {
|
|
484
|
+
this.sweepUnreachable.push(name);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else if (!this.sweepDurablyMissing.includes(cmd)) {
|
|
489
|
+
this.sweepDurablyMissing.push(cmd);
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
280
492
|
}
|
|
281
493
|
async probeCommandCandidates(candidates) {
|
|
282
494
|
for (const candidate of candidates) {
|
|
283
|
-
if (await this.probeCommand(candidate.cmd, candidate.argsPrefix)) {
|
|
495
|
+
if (await this.probeCommand(candidate.cmd, candidate.argsPrefix, candidate.fallback ?? false)) {
|
|
284
496
|
return candidate;
|
|
285
497
|
}
|
|
286
498
|
}
|