@herbertgao/pi-extensions 2026.9.0 → 2026.9.1
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/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
- package/node_modules/@czottmann/pi-automode/README.md +20 -0
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
- package/node_modules/pi-lens/CHANGELOG.md +988 -0
- package/node_modules/pi-lens/README.md +3 -0
- package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +1 -3
- package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -4
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
- package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
- package/node_modules/pi-lens/dist/clients/cache-observability.js +44 -18
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
- package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
- package/node_modules/pi-lens/dist/clients/config-core/index.js +47 -0
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +171 -0
- package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +167 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
- package/node_modules/pi-lens/dist/clients/config-schema.js +197 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
- package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
- package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +14 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -18
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +380 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +13 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -3
- package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
- package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
- package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
- package/node_modules/pi-lens/dist/clients/file-time.js +6 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +112 -9
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
- package/node_modules/pi-lens/dist/clients/format-service.js +6 -1
- package/node_modules/pi-lens/dist/clients/formatters.js +276 -102
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +114 -19
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
- package/node_modules/pi-lens/dist/clients/git-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
- package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +71 -52
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +104 -230
- package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
- package/node_modules/pi-lens/dist/clients/language-registry.js +595 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +160 -2
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +13 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +141 -30
- package/node_modules/pi-lens/dist/clients/lens-engine.js +30 -37
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +59 -6
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +168 -30
- package/node_modules/pi-lens/dist/clients/lsp/config.js +483 -158
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +15 -2
- package/node_modules/pi-lens/dist/clients/lsp/index.js +676 -71
- package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -169
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -16
- package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +9 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
- package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +2 -4
- package/node_modules/pi-lens/dist/clients/module-report.js +20 -45
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -20
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +68 -22
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +4 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +428 -13
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +77 -16
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
- package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +393 -122
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
- package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
- package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
- package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
- package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +15 -4
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
- package/node_modules/pi-lens/dist/clients/read-guard.js +21 -0
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +152 -109
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +18 -2
- package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +139 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +168 -35
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
- package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
- package/node_modules/pi-lens/dist/clients/sanitize.js +7 -1
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
- package/node_modules/pi-lens/dist/clients/sgconfig.js +6 -1
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
- package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +239 -0
- package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
- package/node_modules/pi-lens/dist/clients/tool-policy.js +91 -21
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +86 -48
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
- package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
- package/node_modules/pi-lens/dist/clients/user-notify.js +6 -2
- package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/word-index-store.js +20 -5
- package/node_modules/pi-lens/dist/clients/word-index.js +243 -31
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +3 -0
- package/node_modules/pi-lens/dist/index.js +64335 -54300
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +127 -5
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +95 -24
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +67 -80
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
- package/node_modules/pi-lens/dist/tools/shared.js +0 -1
- package/node_modules/pi-lens/docs/agent-guide.md +64 -1
- package/node_modules/pi-lens/docs/configuration.md +222 -0
- package/node_modules/pi-lens/docs/dependencies.md +3 -3
- package/node_modules/pi-lens/docs/features.md +44 -5
- package/node_modules/pi-lens/docs/language-coverage.md +2 -2
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +27 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +38 -0
- package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +182 -0
- package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
- package/node_modules/pi-lens/package.json +15 -23
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
- package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
- package/node_modules/pi-web-access/CHANGELOG.md +28 -0
- package/node_modules/pi-web-access/README.md +62 -15
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/gemini-search.ts +8 -4
- package/node_modules/pi-web-access/github-extract.ts +242 -1
- package/node_modules/pi-web-access/index.ts +118 -64
- package/node_modules/pi-web-access/mistral-search.ts +281 -0
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/perplexity.ts +14 -1
- package/node_modules/pi-web-access/utils.ts +23 -6
- package/node_modules/pi-web-access/xai-search.ts +96 -33
- package/package.json +5 -5
|
@@ -29,16 +29,17 @@
|
|
|
29
29
|
* - rust-analyzer (Rust LSP) [GitHub release]
|
|
30
30
|
* - golangci-lint (Go linting) [GitHub release]
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
* - yaml-language-server
|
|
34
|
-
* -
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* -
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
32
|
+
* Every other managed tool (including several the list above omits, e.g.
|
|
33
|
+
* bash-language-server, yaml-language-server, svelte-language-server,
|
|
34
|
+
* @prisma/language-server, @vue/language-server, dockerfile-language-server-nodejs,
|
|
35
|
+
* and vscode-css-languageserver — all of which ARE auto-installed) is
|
|
36
|
+
* documented by the `TOOLS` array below, not restated here. #2638: a
|
|
37
|
+
* hand-kept "manual install" list naming specific package specs used to live
|
|
38
|
+
* in this comment, drifted from `TOOLS` (it named a since-unpublished npm
|
|
39
|
+
* package for the CSS entry, and several tools it called "manual" were
|
|
40
|
+
* already auto-install above) and nothing caught it. `TOOLS` is the single
|
|
41
|
+
* source of truth for every id, package, and strategy; deleted rather than
|
|
42
|
+
* re-derived, since a comment cannot be generated from code at write time.
|
|
42
43
|
*
|
|
43
44
|
* Strategies:
|
|
44
45
|
* - npm packages via npx/bun
|
|
@@ -234,7 +235,12 @@ export const TOOLS = [
|
|
|
234
235
|
checkCommand: "typescript-language-server",
|
|
235
236
|
checkArgs: ["--version"],
|
|
236
237
|
installStrategy: "npm",
|
|
237
|
-
|
|
238
|
+
// Pinned below the package's own floor: 6.0.0 declares
|
|
239
|
+
// engines.node >=22.22.2, above the pi host's own floor (pi-lens
|
|
240
|
+
// must never require more than pi does — #2633) — an unpinned
|
|
241
|
+
// install here resolves latest and prints EBADENGINE on any pi
|
|
242
|
+
// host's supported Node. 5.3.0's own floor is engines.node >=20.
|
|
243
|
+
packageName: "typescript-language-server@5.3.0",
|
|
238
244
|
binaryName: "typescript-language-server",
|
|
239
245
|
},
|
|
240
246
|
{
|
|
@@ -440,7 +446,7 @@ export const TOOLS = [
|
|
|
440
446
|
checkCommand: "vscode-html-language-server",
|
|
441
447
|
checkArgs: ["--version"],
|
|
442
448
|
installStrategy: "npm",
|
|
443
|
-
packageName: "vscode-
|
|
449
|
+
packageName: "vscode-langservers-extracted",
|
|
444
450
|
binaryName: "vscode-html-language-server",
|
|
445
451
|
},
|
|
446
452
|
{
|
|
@@ -529,7 +535,7 @@ export const TOOLS = [
|
|
|
529
535
|
checkCommand: "vscode-css-language-server",
|
|
530
536
|
checkArgs: ["--version"],
|
|
531
537
|
installStrategy: "npm",
|
|
532
|
-
packageName: "vscode-
|
|
538
|
+
packageName: "vscode-langservers-extracted",
|
|
533
539
|
binaryName: "vscode-css-language-server",
|
|
534
540
|
},
|
|
535
541
|
{
|
|
@@ -1823,7 +1829,7 @@ const lspTransportRequiredMatcher = /Connection (?:input|output) stream is not s
|
|
|
1823
1829
|
* correctly reports "no pin" rather than mistaking the scope for a version.
|
|
1824
1830
|
* Returns undefined when packageName has no explicit `@version` suffix.
|
|
1825
1831
|
*/
|
|
1826
|
-
function parsePinnedVersion(packageName) {
|
|
1832
|
+
export function parsePinnedVersion(packageName) {
|
|
1827
1833
|
const at = packageName.lastIndexOf("@");
|
|
1828
1834
|
if (at <= 0)
|
|
1829
1835
|
return undefined;
|
|
@@ -3303,10 +3309,10 @@ async function refreshPackageManagerManagedTool(tool) {
|
|
|
3303
3309
|
const installed = tool.installStrategy === "pip"
|
|
3304
3310
|
? // `-U` is the whole fix: without it pip treats the installed copy as
|
|
3305
3311
|
// satisfying the requirement and the day-one version never moves.
|
|
3306
|
-
await installPipTool(tool.packageName, { upgrade: true })
|
|
3312
|
+
await installPipTool(tool.id, tool.packageName, { upgrade: true })
|
|
3307
3313
|
: // `gem install` always fetches the newest version that satisfies the
|
|
3308
3314
|
// requirement, so the install command IS the upgrade command.
|
|
3309
|
-
await installGemTool(tool.packageName);
|
|
3315
|
+
await installGemTool(tool.id, tool.packageName);
|
|
3310
3316
|
if (!installed) {
|
|
3311
3317
|
return {
|
|
3312
3318
|
ok: false,
|
|
@@ -3734,7 +3740,24 @@ async function installArchiveTool(tool) {
|
|
|
3734
3740
|
return undefined;
|
|
3735
3741
|
}
|
|
3736
3742
|
}
|
|
3737
|
-
|
|
3743
|
+
/**
|
|
3744
|
+
* Record a genuine package-manager install exception against `toolId` and
|
|
3745
|
+
* log it — the one place `installNpmTool`/`installPipTool`/`installGemTool`'s
|
|
3746
|
+
* catch blocks funnel through (#2661 review F3/Sonar: the three catches were
|
|
3747
|
+
* byte-identical but for the strategy label, a duplication SonarCloud's new-
|
|
3748
|
+
* code gate correctly flagged). The real error (registry E404, EBADENGINE, a
|
|
3749
|
+
* network failure, "no version satisfies…") lands in `installFailureReasons`
|
|
3750
|
+
* so a caller that distinguishes a genuine installer defect from a policy
|
|
3751
|
+
* decline (the tool-smoke lane, #2638) sees the actual message, never the
|
|
3752
|
+
* generic fallback `finishInstallAttempt` uses when nothing set it.
|
|
3753
|
+
*/
|
|
3754
|
+
function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
|
|
3755
|
+
const message = err.message;
|
|
3756
|
+
logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
|
|
3757
|
+
installFailureReasons.set(toolId, message);
|
|
3758
|
+
return undefined;
|
|
3759
|
+
}
|
|
3760
|
+
async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 10_000) {
|
|
3738
3761
|
try {
|
|
3739
3762
|
// Ensure tools directory exists
|
|
3740
3763
|
await fs.mkdir(TOOLS_DIR, { recursive: true });
|
|
@@ -3858,14 +3881,26 @@ async function installNpmTool(packageName, binaryName, verificationArgs = ["--ve
|
|
|
3858
3881
|
return binPath;
|
|
3859
3882
|
}
|
|
3860
3883
|
catch (err) {
|
|
3861
|
-
|
|
3862
|
-
return undefined;
|
|
3884
|
+
return recordPackageManagerInstallException(toolId, "npm", packageName, err);
|
|
3863
3885
|
}
|
|
3864
3886
|
}
|
|
3887
|
+
/**
|
|
3888
|
+
* The pip command ladder `installPipTool` tries, per platform, in priority
|
|
3889
|
+
* order — exported so a caller that needs to know "is ANY pip toolchain
|
|
3890
|
+
* reachable on this runner" (without running an install) probes the SAME
|
|
3891
|
+
* candidates rather than a second, narrower guess (#2661 review F4/S5: the
|
|
3892
|
+
* tool-smoke lane's own presence check tried bare `pip` only and missed a
|
|
3893
|
+
* `pip3`-only or python-module-only runner).
|
|
3894
|
+
*/
|
|
3895
|
+
export function pipCommandCandidates() {
|
|
3896
|
+
return process.platform === "win32"
|
|
3897
|
+
? ["pip", "py", "python"]
|
|
3898
|
+
: ["pip3", "pip", "python3", "python"];
|
|
3899
|
+
}
|
|
3865
3900
|
/**
|
|
3866
3901
|
* Install a pip package tool
|
|
3867
3902
|
*/
|
|
3868
|
-
async function installPipTool(packageName,
|
|
3903
|
+
async function installPipTool(toolId, packageName,
|
|
3869
3904
|
/**
|
|
3870
3905
|
* Add `-U`, turning the install into an upgrade. Without it `pip install`
|
|
3871
3906
|
* treats an already-present package as satisfied and leaves the day-one
|
|
@@ -3880,30 +3915,16 @@ options = {}) {
|
|
|
3880
3915
|
const verb = options.upgrade
|
|
3881
3916
|
? ["install", "-U", "--user"]
|
|
3882
3917
|
: ["install", "--user"];
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
},
|
|
3894
|
-
]
|
|
3895
|
-
: [
|
|
3896
|
-
{ command: "pip3", args: [...verb, packageName] },
|
|
3897
|
-
{ command: "pip", args: [...verb, packageName] },
|
|
3898
|
-
{
|
|
3899
|
-
command: "python3",
|
|
3900
|
-
args: ["-m", "pip", ...verb, packageName],
|
|
3901
|
-
},
|
|
3902
|
-
{
|
|
3903
|
-
command: "python",
|
|
3904
|
-
args: ["-m", "pip", ...verb, packageName],
|
|
3905
|
-
},
|
|
3906
|
-
];
|
|
3918
|
+
// Built from `pipCommandCandidates()` — the single source of truth this
|
|
3919
|
+
// module and any other caller (the tool-smoke lane's toolchain-presence
|
|
3920
|
+
// probe, #2661 review) share, rather than a second, independently
|
|
3921
|
+
// maintained ladder that can drift.
|
|
3922
|
+
const pipCandidates = pipCommandCandidates().map((command) => ({
|
|
3923
|
+
command,
|
|
3924
|
+
args: command === "pip" || command === "pip3"
|
|
3925
|
+
? [...verb, packageName]
|
|
3926
|
+
: ["-m", "pip", ...verb, packageName],
|
|
3927
|
+
}));
|
|
3907
3928
|
let lastError = "";
|
|
3908
3929
|
for (const candidate of pipCandidates) {
|
|
3909
3930
|
const pipResult = await safeSpawnAsync(candidate.command, candidate.args, {
|
|
@@ -4000,11 +4021,10 @@ options = {}) {
|
|
|
4000
4021
|
throw new Error(`Failed to install ${packageName}: no usable pip command found (${lastError || "unknown error"})`);
|
|
4001
4022
|
}
|
|
4002
4023
|
catch (err) {
|
|
4003
|
-
|
|
4004
|
-
return undefined;
|
|
4024
|
+
return recordPackageManagerInstallException(toolId, "pip", packageName, err);
|
|
4005
4025
|
}
|
|
4006
4026
|
}
|
|
4007
|
-
async function installGemTool(packageName) {
|
|
4027
|
+
async function installGemTool(toolId, packageName) {
|
|
4008
4028
|
try {
|
|
4009
4029
|
const gemResult = await safeSpawnAsync("gem", ["install", packageName, "--no-document"], {
|
|
4010
4030
|
timeout: 120_000,
|
|
@@ -4021,8 +4041,7 @@ async function installGemTool(packageName) {
|
|
|
4021
4041
|
return packageName;
|
|
4022
4042
|
}
|
|
4023
4043
|
catch (err) {
|
|
4024
|
-
|
|
4025
|
-
return undefined;
|
|
4044
|
+
return recordPackageManagerInstallException(toolId, "gem", packageName, err);
|
|
4026
4045
|
}
|
|
4027
4046
|
}
|
|
4028
4047
|
/**
|
|
@@ -4113,7 +4132,7 @@ export async function installTool(toolId) {
|
|
|
4113
4132
|
case "npm": {
|
|
4114
4133
|
if (!tool.packageName || !tool.binaryName)
|
|
4115
4134
|
return false;
|
|
4116
|
-
const npmPath = await installNpmTool(tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
4135
|
+
const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
4117
4136
|
if (npmPath !== undefined) {
|
|
4118
4137
|
// #1746 review F4: an install just resolved this package's range
|
|
4119
4138
|
// against the registry, so record it as freshly checked. Otherwise a
|
|
@@ -4139,13 +4158,13 @@ export async function installTool(toolId) {
|
|
|
4139
4158
|
case "pip": {
|
|
4140
4159
|
if (!tool.packageName)
|
|
4141
4160
|
return false;
|
|
4142
|
-
const pipPath = await installPipTool(tool.packageName);
|
|
4161
|
+
const pipPath = await installPipTool(tool.id, tool.packageName);
|
|
4143
4162
|
return finishInstallAttempt(tool.id, pipPath !== undefined, startedAt);
|
|
4144
4163
|
}
|
|
4145
4164
|
case "gem": {
|
|
4146
4165
|
if (!tool.packageName)
|
|
4147
4166
|
return false;
|
|
4148
|
-
const gemPath = await installGemTool(tool.packageName);
|
|
4167
|
+
const gemPath = await installGemTool(tool.id, tool.packageName);
|
|
4149
4168
|
return finishInstallAttempt(tool.id, gemPath !== undefined, startedAt);
|
|
4150
4169
|
}
|
|
4151
4170
|
case "github": {
|
|
@@ -67,11 +67,12 @@ import * as path from "node:path";
|
|
|
67
67
|
import { writeFileAtomicAsync } from "./atomic-write.js";
|
|
68
68
|
import { sweepOwnStagingFiles, } from "./atomic-write-staging.js";
|
|
69
69
|
import { acquireQuarantinePidFileLock } from "./bounded-pid-file-lock.js";
|
|
70
|
-
import {
|
|
70
|
+
import { unrefChildAndPipes, } from "./child-unref.js";
|
|
71
71
|
import { incrementDegradationCount, recordDegradationOnce, } from "./degradation-ledger.js";
|
|
72
72
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
73
73
|
import { isInstanceRegistryEnabled, pruneDeadInstances as pruneDeadRegistryInstances, readInstanceRegistry, } from "./instance-registry.js";
|
|
74
74
|
import { logLatency } from "./latency-logger.js";
|
|
75
|
+
import { queryProcessTable, windowsExe } from "./process-snapshot.js";
|
|
75
76
|
const isWindows = process.platform === "win32";
|
|
76
77
|
/**
|
|
77
78
|
* KILL eligibility: pid-confirmed-dead ONLY. Heartbeat staleness must never
|
|
@@ -249,31 +250,14 @@ export async function sweepAtomicWriteStages(directories, options = {}) {
|
|
|
249
250
|
const isPidAlive = options.isPidAlive ?? realIsPidAlive;
|
|
250
251
|
return sweepOwnStagingFiles(directories, { maxEntries, isPidAlive });
|
|
251
252
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
|
|
260
|
-
if (fs.existsSync("/bin/ps"))
|
|
261
|
-
return "/bin/ps";
|
|
262
|
-
if (fs.existsSync("/usr/bin/ps"))
|
|
263
|
-
return "/usr/bin/ps";
|
|
264
|
-
return "/bin/ps";
|
|
265
|
-
}
|
|
266
|
-
/** Escape a value for embedding in a WQL LIKE clause: WQL uses `'` as the
|
|
267
|
-
* string delimiter (doubled to escape) and `%`/`_` as wildcards — the marker
|
|
268
|
-
* is an opaque path string, so escape all three before interpolating. */
|
|
269
|
-
function escapeWqlLikeValue(value) {
|
|
270
|
-
return value.replaceAll("'", "''").replaceAll(/[%_]/g, (ch) => `[${ch}]`);
|
|
271
|
-
}
|
|
272
|
-
/** Escape a value for a WQL EQUALITY comparison: only the string delimiter
|
|
273
|
-
* needs escaping — `%`/`_` are literal outside `LIKE`. */
|
|
274
|
-
function escapeWqlStringValue(value) {
|
|
275
|
-
return value.replaceAll("'", "''");
|
|
276
|
-
}
|
|
253
|
+
/**
|
|
254
|
+
* Interpreter resolution, WQL escaping and the platform listing itself all
|
|
255
|
+
* live in the ONE process-table seam (#2443): `scripts/lib/process-scan.mjs`,
|
|
256
|
+
* reached from clients/ through `clients/process-snapshot.ts`. This file used
|
|
257
|
+
* to carry its own copy of each, which is how the three queries below drifted
|
|
258
|
+
* apart from the two in `clients/resource-sampler.ts` and the two in
|
|
259
|
+
* `scripts/`. `windowsExe` is re-exported by the seam and imported above.
|
|
260
|
+
*/
|
|
277
261
|
/** Search running processes whose command line contains `marker` (Windows,
|
|
278
262
|
* via CIM/WQL). Returns matching pids. Best-effort: any failure ⇒ [], but a
|
|
279
263
|
* failure is RECORDED (#1857 class sweep) — "the search found no orphan" and
|
|
@@ -282,14 +266,26 @@ function escapeWqlStringValue(value) {
|
|
|
282
266
|
async function findPidsByMarkerWindows(marker) {
|
|
283
267
|
if (!isWindows || !marker)
|
|
284
268
|
return [];
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
269
|
+
const result = await queryProcessTable({
|
|
270
|
+
fields: ["pid"],
|
|
271
|
+
filter: { column: "CommandLine", op: "like", values: [marker] },
|
|
272
|
+
// The query's own powershell.exe command line embeds the marker, so
|
|
273
|
+
// without this exclusion the search matches itself.
|
|
274
|
+
excludeSelfPid: true,
|
|
275
|
+
}, {
|
|
276
|
+
timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
|
|
277
|
+
// #2527 review F2: omitting `onTimeout` falls through to
|
|
278
|
+
// child-unref.ts's default — one bare, unverified `child.kill()`, no
|
|
279
|
+
// tree kill, no identity-carrying record — inside the orphan backstop
|
|
280
|
+
// path, the exact abandonment `terminateScannerChild`'s own doc
|
|
281
|
+
// comment says it exists to prevent. This query shares the backstop's
|
|
282
|
+
// scanner identity and budget (same producer as
|
|
283
|
+
// `enumerateManagedProcesses`), so it gets the same kind.
|
|
284
|
+
onTimeout: (child) => terminateScannerChild(child, {
|
|
285
|
+
kind: "orphan-backstop-scanner-escalated",
|
|
286
|
+
timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
293
289
|
if (result.status !== "ok") {
|
|
294
290
|
recordDegradationOnce({
|
|
295
291
|
kind: "orphan-backstop-scan-failed",
|
|
@@ -297,10 +293,7 @@ async function findPidsByMarkerWindows(marker) {
|
|
|
297
293
|
reason: `marker command-line search ${result.status}`,
|
|
298
294
|
});
|
|
299
295
|
}
|
|
300
|
-
return result.
|
|
301
|
-
.split(/\r?\n/)
|
|
302
|
-
.map((line) => Number(line.trim()))
|
|
303
|
-
.filter((n) => Number.isFinite(n) && n > 0);
|
|
296
|
+
return result.rows.map((row) => row.pid);
|
|
304
297
|
}
|
|
305
298
|
/** Fetch command lines for a set of pids in one query (Windows: CIM; POSIX:
|
|
306
299
|
* `ps`). Returns a pid → command-line map; pids that can't be resolved are
|
|
@@ -317,59 +310,35 @@ async function queryCommandLines(pids) {
|
|
|
317
310
|
const map = new Map();
|
|
318
311
|
if (valid.length === 0)
|
|
319
312
|
return map;
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
313
|
+
const result = await queryProcessTable({
|
|
314
|
+
fields: ["pid", "command"],
|
|
315
|
+
filter: { column: "ProcessId", op: "eq", values: valid },
|
|
316
|
+
}, {
|
|
317
|
+
timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
|
|
318
|
+
// #2527 review F2: same rationale as findPidsByMarkerWindows above —
|
|
319
|
+
// route through the reaper's own tree-kill-and-verify machinery with
|
|
320
|
+
// the backstop's kind rather than falling through to the unverified
|
|
321
|
+
// default kill with no ledger record.
|
|
322
|
+
onTimeout: (child) => terminateScannerChild(child, {
|
|
323
|
+
kind: "orphan-backstop-scanner-escalated",
|
|
324
|
+
timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
|
|
325
|
+
}),
|
|
326
|
+
});
|
|
327
|
+
// POSIX `ps -p` exits nonzero when NONE of the requested pids exist, which
|
|
328
|
+
// is a legitimate clean result for this caller, so that one status is not
|
|
329
|
+
// recorded on that platform. The shared collector calls it an exit failure;
|
|
330
|
+
// this caller's command contract makes it the expected empty table. Windows
|
|
331
|
+
// CIM has no such convention, so an exit failure there is a real failure.
|
|
332
|
+
const psReportedNoSuchPid = !isWindows && result.status === "exit-error";
|
|
333
|
+
if (result.status !== "ok" && !psReportedNoSuchPid) {
|
|
323
334
|
recordDegradationOnce({
|
|
324
335
|
kind: "orphan-backstop-scan-failed",
|
|
325
336
|
subject: "identity-query",
|
|
326
|
-
reason: `command-line identity query ${status}; reap suppressed this sweep`,
|
|
337
|
+
reason: `command-line identity query ${result.status}; reap suppressed this sweep`,
|
|
327
338
|
});
|
|
328
|
-
};
|
|
329
|
-
if (isWindows) {
|
|
330
|
-
const filter = valid.map((p) => `ProcessId=${p}`).join(" OR ");
|
|
331
|
-
const psScript = `Get-CimInstance Win32_Process -Filter "${filter}" ` +
|
|
332
|
-
`| ForEach-Object { "$($_.ProcessId)\t$($_.CommandLine)" }`;
|
|
333
|
-
const powershell = windowsExe("WindowsPowerShell\\v1.0\\powershell.exe");
|
|
334
|
-
const result = await spawnCollectStdoutResult(powershell, ["-NoProfile", "-NonInteractive", "-Command", psScript], { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }, { timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS });
|
|
335
|
-
noteFailure(result.status);
|
|
336
|
-
for (const line of result.stdout.split(/\r?\n/)) {
|
|
337
|
-
const tab = line.indexOf("\t");
|
|
338
|
-
if (tab <= 0)
|
|
339
|
-
continue;
|
|
340
|
-
const pid = Number(line.slice(0, tab).trim());
|
|
341
|
-
if (Number.isFinite(pid) && pid > 0)
|
|
342
|
-
map.set(pid, line.slice(tab + 1));
|
|
343
|
-
}
|
|
344
|
-
return map;
|
|
345
|
-
}
|
|
346
|
-
const result = await spawnCollectStdoutResult(posixPsPath(), ["-p", valid.join(","), "-o", "pid=,args="], { shell: false, stdio: ["ignore", "pipe", "ignore"] }, { timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS });
|
|
347
|
-
// `ps -p` exits nonzero when NONE of the pids exist, which is a legitimate
|
|
348
|
-
// clean result here, so only spawn/timeout failures are recorded. The
|
|
349
|
-
// shared collector calls that an exit failure, but this caller's command
|
|
350
|
-
// contract makes that status the expected empty-table result.
|
|
351
|
-
if (result.status !== "exit-error")
|
|
352
|
-
noteFailure(result.status);
|
|
353
|
-
for (const line of result.stdout.split(/\r?\n/)) {
|
|
354
|
-
// Linear parse (S8786/S6594: avoid regex backtracking on
|
|
355
|
-
// attacker-lengthenable ps output) — trim leading whitespace,
|
|
356
|
-
// then split on the first whitespace run: " 1234 args here".
|
|
357
|
-
const trimmed = line.trimStart();
|
|
358
|
-
if (!trimmed)
|
|
359
|
-
continue;
|
|
360
|
-
let i = 0;
|
|
361
|
-
while (i < trimmed.length && trimmed[i] >= "0" && trimmed[i] <= "9")
|
|
362
|
-
i++;
|
|
363
|
-
if (i === 0)
|
|
364
|
-
continue;
|
|
365
|
-
const pidStr = trimmed.slice(0, i);
|
|
366
|
-
let j = i;
|
|
367
|
-
while (j < trimmed.length && (trimmed[j] === " " || trimmed[j] === "\t"))
|
|
368
|
-
j++;
|
|
369
|
-
const pid = Number(pidStr);
|
|
370
|
-
if (Number.isFinite(pid) && pid > 0)
|
|
371
|
-
map.set(pid, trimmed.slice(j));
|
|
372
339
|
}
|
|
340
|
+
for (const row of result.rows)
|
|
341
|
+
map.set(row.pid, row.command);
|
|
373
342
|
return map;
|
|
374
343
|
}
|
|
375
344
|
/**
|
|
@@ -605,104 +574,42 @@ export function partitionBackstopCandidates(processes, registry, isPidAlive, opt
|
|
|
605
574
|
}
|
|
606
575
|
/** Enumerate live OS processes whose command line contains one of
|
|
607
576
|
* `MANAGED_BINARY_NAMES` (#658), independent of the instance registry.
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
577
|
+
* One projected process-table query through the shared seam, narrowed to
|
|
578
|
+
* managed binaries in JS; returns `{pid, parentPid, command, ageMs}` rows.
|
|
579
|
+
* Best-effort: any failure ⇒ [], with the scan status carried alongside so
|
|
580
|
+
* an empty table is never mistaken for a scan that did not run. */
|
|
611
581
|
async function enumerateManagedProcesses(options = {}) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const secondTab = line.indexOf("\t", firstTab + 1);
|
|
644
|
-
if (secondTab <= 0)
|
|
645
|
-
continue;
|
|
646
|
-
const thirdTab = line.indexOf("\t", secondTab + 1);
|
|
647
|
-
if (thirdTab <= 0)
|
|
648
|
-
continue;
|
|
649
|
-
const pid = Number(line.slice(0, firstTab).trim());
|
|
650
|
-
const parentPid = Number(line.slice(firstTab + 1, secondTab).trim());
|
|
651
|
-
const ageMs = parseNonNegativeMs(line.slice(secondTab + 1, thirdTab));
|
|
652
|
-
const command = line.slice(thirdTab + 1);
|
|
653
|
-
if (!Number.isFinite(pid) || pid <= 0)
|
|
654
|
-
continue;
|
|
655
|
-
processes.push({ pid, parentPid, command, ageMs });
|
|
656
|
-
}
|
|
657
|
-
return narrowToManagedBinaries(processes, result);
|
|
658
|
-
}
|
|
659
|
-
// POSIX: enumerate everything, filter in JS by managed-name substring —
|
|
660
|
-
// there is no single-query WQL-style server-side filter available.
|
|
661
|
-
// `etime` is the POSIX-standard elapsed-time column (Linux and macOS both
|
|
662
|
-
// support it; `etimes` is Linux-only), and supplies the spawn-grace age.
|
|
663
|
-
const result = await spawnCollectStdoutResult(posixPsPath(), [...POSIX_PS_ARGS], { shell: false, stdio: ["ignore", "pipe", "ignore"] }, collect);
|
|
664
|
-
const processes = [];
|
|
665
|
-
for (const line of result.stdout.split(/\r?\n/)) {
|
|
666
|
-
// Linear parse (S8786/S6594): " pid ppid etime args here".
|
|
667
|
-
const trimmed = line.trimStart();
|
|
668
|
-
if (!trimmed)
|
|
669
|
-
continue;
|
|
670
|
-
let i = 0;
|
|
671
|
-
while (i < trimmed.length && trimmed[i] >= "0" && trimmed[i] <= "9")
|
|
672
|
-
i++;
|
|
673
|
-
if (i === 0)
|
|
674
|
-
continue;
|
|
675
|
-
const pid = Number(trimmed.slice(0, i));
|
|
676
|
-
let rest = trimmed.slice(i);
|
|
677
|
-
let k = 0;
|
|
678
|
-
while (k < rest.length && (rest[k] === " " || rest[k] === "\t"))
|
|
679
|
-
k++;
|
|
680
|
-
rest = rest.slice(k);
|
|
681
|
-
let j = 0;
|
|
682
|
-
while (j < rest.length && rest[j] >= "0" && rest[j] <= "9")
|
|
683
|
-
j++;
|
|
684
|
-
if (j === 0)
|
|
685
|
-
continue;
|
|
686
|
-
const parentPid = Number(rest.slice(0, j));
|
|
687
|
-
rest = rest.slice(j);
|
|
688
|
-
let m = 0;
|
|
689
|
-
while (m < rest.length && (rest[m] === " " || rest[m] === "\t"))
|
|
690
|
-
m++;
|
|
691
|
-
rest = rest.slice(m);
|
|
692
|
-
// etime token: a non-whitespace run of digits, ':' and '-'.
|
|
693
|
-
let e = 0;
|
|
694
|
-
while (e < rest.length && rest[e] !== " " && rest[e] !== "\t")
|
|
695
|
-
e++;
|
|
696
|
-
const ageMs = ageMsFromPosixEtime(rest.slice(0, e));
|
|
697
|
-
let n = e;
|
|
698
|
-
while (n < rest.length && (rest[n] === " " || rest[n] === "\t"))
|
|
699
|
-
n++;
|
|
700
|
-
const args = rest.slice(n);
|
|
701
|
-
if (!Number.isFinite(pid) || pid <= 0)
|
|
702
|
-
continue;
|
|
703
|
-
processes.push({ pid, parentPid, command: args, ageMs });
|
|
704
|
-
}
|
|
705
|
-
return narrowToManagedBinaries(processes, result);
|
|
582
|
+
// #1857: `Name = '…'` equality replaces eight leading-wildcard
|
|
583
|
+
// `CommandLine LIKE '%…%'` clauses, and the projection carries an age so
|
|
584
|
+
// the spawn-grace guard has something to reason from. The image-name set is
|
|
585
|
+
// DERIVED from MANAGED_BINARIES (MANAGED_IMAGE_NAMES), so adding a managed
|
|
586
|
+
// binary cannot leave a stale parallel list behind.
|
|
587
|
+
//
|
|
588
|
+
// POSIX `ps` cannot express that filter — the seam reports it as
|
|
589
|
+
// `serverSideFiltered: false` and hands back the whole table — which is why
|
|
590
|
+
// `narrowToManagedBinaries` runs on BOTH platforms rather than only on the
|
|
591
|
+
// POSIX branch. Windows over-collects for its own reason anyway
|
|
592
|
+
// (`node.exe` is a superset image name covering every node process on the
|
|
593
|
+
// machine).
|
|
594
|
+
const scanTimeoutMs = options.timeoutMs ?? BACKSTOP_SCAN_TIMEOUT_MS;
|
|
595
|
+
const result = await queryProcessTable({
|
|
596
|
+
fields: ["pid", "ppid", "ageMs", "command"],
|
|
597
|
+
filter: { column: "Name", op: "eq", values: MANAGED_IMAGE_NAMES },
|
|
598
|
+
}, {
|
|
599
|
+
timeoutMs: scanTimeoutMs,
|
|
600
|
+
onTimeout: (child) => terminateScannerChild(child, {
|
|
601
|
+
verifyAttempts: options.verifyAttempts,
|
|
602
|
+
verifyIntervalMs: options.verifyIntervalMs,
|
|
603
|
+
kind: "orphan-backstop-scanner-escalated",
|
|
604
|
+
timeoutMs: scanTimeoutMs,
|
|
605
|
+
}),
|
|
606
|
+
});
|
|
607
|
+
return narrowToManagedBinaries(result.rows.map((row) => ({
|
|
608
|
+
pid: row.pid,
|
|
609
|
+
parentPid: row.ppid,
|
|
610
|
+
command: row.command,
|
|
611
|
+
ageMs: row.ageMs,
|
|
612
|
+
})), result);
|
|
706
613
|
}
|
|
707
614
|
/**
|
|
708
615
|
* The ONE place a raw OS-process snapshot is narrowed to managed binaries.
|
|
@@ -730,6 +637,18 @@ function narrowToManagedBinaries(processes, result) {
|
|
|
730
637
|
* exists to fix, so the scanner now gets exactly what an orphan gets:
|
|
731
638
|
* `taskkill /F /T` or a POSIX group kill, then a verified liveness poll. The
|
|
732
639
|
* escalation is recorded with the scanner's identity, never swallowed.
|
|
640
|
+
*
|
|
641
|
+
* The kill attempt itself is real regardless of what happens next: this
|
|
642
|
+
* handler is invoked the instant the caller's timer elapses, which can race
|
|
643
|
+
* a query child that is genuinely about to close successfully (its own
|
|
644
|
+
* "close" event may still win the overall settle — see
|
|
645
|
+
* `child-unref.ts#spawnCollectStdoutResult`, which resolves on whichever of
|
|
646
|
+
* "close" or the timeout handler's own settle lands first). This function has
|
|
647
|
+
* no way to observe that outcome — it runs strictly BEFORE the caller's
|
|
648
|
+
* promise resolves — so it always records the escalation it actually
|
|
649
|
+
* attempted; callers whose escalation kind can fire on that race classify it
|
|
650
|
+
* as informational rather than a warning (see
|
|
651
|
+
* `degradation-ledger.ts`'s `resource-sampler-scanner-escalated` doc comment).
|
|
733
652
|
*/
|
|
734
653
|
export async function terminateScannerChild(child, options) {
|
|
735
654
|
const pid = child.pid;
|
|
@@ -740,9 +659,9 @@ export async function terminateScannerChild(child, options) {
|
|
|
740
659
|
})
|
|
741
660
|
: "invalid";
|
|
742
661
|
incrementDegradationCount({
|
|
743
|
-
kind:
|
|
662
|
+
kind: options.kind,
|
|
744
663
|
subject: `${isWindows ? "win32-cim" : "posix-ps"}#${pid ?? "no-pid"}`,
|
|
745
|
-
reason: `scan exceeded its timeout; tree kill reported ${outcome}`,
|
|
664
|
+
reason: `scan exceeded its ${options.timeoutMs}ms timeout; tree kill reported ${outcome}`,
|
|
746
665
|
});
|
|
747
666
|
return outcome;
|
|
748
667
|
}
|
|
@@ -755,51 +674,6 @@ function matchesManagedBinary(command) {
|
|
|
755
674
|
const lower = command.toLowerCase();
|
|
756
675
|
return MANAGED_BINARY_NAMES.some((name) => lower.includes(name.toLowerCase()));
|
|
757
676
|
}
|
|
758
|
-
/**
|
|
759
|
-
* Parse the Windows age column: a non-negative integer millisecond count, or
|
|
760
|
-
* anything else. A negative or malformed value is `undefined`, not zero and
|
|
761
|
-
* not a clamp — a nonsensical age means the age is UNKNOWN, and the grace
|
|
762
|
-
* guard must spare an unknown-age process rather than reason from a number it
|
|
763
|
-
* cannot trust. That rule is what surfaced the local-time tick bug during the
|
|
764
|
-
* host probe instead of silently sparing everything.
|
|
765
|
-
*/
|
|
766
|
-
function parseNonNegativeMs(raw) {
|
|
767
|
-
const token = raw.trim();
|
|
768
|
-
if (!/^\d+$/.test(token))
|
|
769
|
-
return undefined;
|
|
770
|
-
const value = Number(token);
|
|
771
|
-
return Number.isFinite(value) ? value : undefined;
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* POSIX enumeration columns. Exported so a real-`ps` test can assert this
|
|
775
|
-
* exact argument vector is accepted by the host's `ps` — a rejected column
|
|
776
|
-
* would make the whole backstop silently return zero rows, and this is the
|
|
777
|
-
* one part of the fix that cannot be checked from a Windows dev machine.
|
|
778
|
-
*/
|
|
779
|
-
export const POSIX_PS_ARGS = [
|
|
780
|
-
"-eo",
|
|
781
|
-
"pid=,ppid=,etime=,args=",
|
|
782
|
-
];
|
|
783
|
-
/** Parse `ps -o etime` output: `[[dd-]hh:]mm:ss`. Returns undefined for any
|
|
784
|
-
* shape the column did not produce (a `ps` without the column emits the
|
|
785
|
-
* command line where the token was expected). */
|
|
786
|
-
export function ageMsFromPosixEtime(raw) {
|
|
787
|
-
const token = raw.trim();
|
|
788
|
-
if (!/^(?:\d+-)?(?:\d+:)?\d+:\d+$/.test(token))
|
|
789
|
-
return undefined;
|
|
790
|
-
let days = 0;
|
|
791
|
-
let rest = token;
|
|
792
|
-
const dash = rest.indexOf("-");
|
|
793
|
-
if (dash >= 0) {
|
|
794
|
-
days = Number(rest.slice(0, dash));
|
|
795
|
-
rest = rest.slice(dash + 1);
|
|
796
|
-
}
|
|
797
|
-
const parts = rest.split(":").map(Number);
|
|
798
|
-
if (parts.some((part) => !Number.isFinite(part)))
|
|
799
|
-
return undefined;
|
|
800
|
-
const [hours, minutes, seconds] = parts.length === 3 ? parts : [0, parts[0], parts[1]];
|
|
801
|
-
return ((days * 24 + hours) * 60 * 60 + minutes * 60 + seconds) * 1000;
|
|
802
|
-
}
|
|
803
677
|
/**
|
|
804
678
|
* Fire-and-forget REGISTRY-INDEPENDENT backstop sweep (#658): finds
|
|
805
679
|
* pi-lens-managed LSP/scanner processes the registry-driven `sweepOrphans`
|