@sanqianx/project-knowledge 5.0.24 → 5.0.25
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/.agents/plugins/marketplace.json +20 -20
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +1361 -1353
- package/LICENSE +201 -201
- package/NOTICE +5 -5
- package/README.md +349 -349
- package/_modules/knowledge-engine/package.json +1 -1
- package/_modules/knowledge-engine/src/coordinator.js +48 -3
- package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -140
- package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +238 -238
- package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -26
- package/_modules/knowledge-engine/src/runtime/commit-prompt.js +193 -193
- package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +394 -394
- package/_modules/knowledge-engine/src/runtime/contracts.js +282 -282
- package/_modules/knowledge-engine/src/runtime/conversation-store.js +389 -389
- package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -228
- package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -62
- package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +505 -505
- package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +400 -400
- package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +171 -171
- package/_modules/knowledge-engine/src/runtime/scanner.js +298 -298
- package/_modules/knowledge-engine/ui/app.js +23 -2
- package/_site/README.md +30 -30
- package/_site/_test/agent-capture-mcp-ui-test.js +68 -68
- package/_site/_test/agent-capture-ui-test.js +101 -101
- package/_site/_test/ai-profile-resolver-test.js +137 -137
- package/_site/_test/ai-profile-test.js +75 -75
- package/_site/_test/ai-vendor-presets-test.js +91 -91
- package/_site/_test/automatic-context-test.js +70 -70
- package/_site/_test/automation-queue-test.js +14 -14
- package/_site/_test/automation-ui-test.js +40 -40
- package/_site/_test/background-task-registry-test.js +43 -43
- package/_site/_test/baseline-schema-test.js +91 -91
- package/_site/_test/bin-cli-test.js +230 -230
- package/_site/_test/bridge-adapter-test.js +95 -95
- package/_site/_test/bridge-consumer-service-test.js +310 -310
- package/_site/_test/candidate-bin-content-test.js +35 -35
- package/_site/_test/candidate-product-assets-test.js +13 -13
- package/_site/_test/candidate-release-contract-test.js +14 -14
- package/_site/_test/capture-hook-dedup-test.js +158 -158
- package/_site/_test/cdp-browser-failure-test.js +103 -103
- package/_site/_test/chat-claudecodeui-match-test.js +92 -92
- package/_site/_test/claude-executable-discovery-test.js +90 -90
- package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
- package/_site/_test/claude-workbench-test.js +174 -174
- package/_site/_test/cli-instance-management-test.js +62 -62
- package/_site/_test/client-capture-restart-e2e-test.js +189 -189
- package/_site/_test/codex-conversation-projection-test.js +60 -60
- package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
- package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
- package/_site/_test/codex-notify-fanout-test.js +141 -134
- package/_site/_test/codex-notify-upgrade-test.js +253 -253
- package/_site/_test/commit-boundary-freeze-test.js +147 -147
- package/_site/_test/commit-conversation-binding-test.js +99 -99
- package/_site/_test/commit-evidence-test.js +110 -110
- package/_site/_test/configuration-generation-test.js +22 -22
- package/_site/_test/context-pack-test.js +200 -200
- package/_site/_test/conversation-api-test.js +119 -119
- package/_site/_test/conversation-origin-test.js +15 -15
- package/_site/_test/conversation-store-test.js +138 -138
- package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
- package/_site/_test/desktop-browser-compat-test.js +28 -28
- package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
- package/_site/_test/embedding-config-test.js +32 -32
- package/_site/_test/explicit-commit-processor-test.js +37 -37
- package/_site/_test/fixtures/fake-browser-exit.js +10 -10
- package/_site/_test/fixtures/fake-browser-hang.js +10 -10
- package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
- package/_site/_test/fixtures/make-git-repos.js +92 -92
- package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
- package/_site/_test/folder-picker-output-test.js +19 -19
- package/_site/_test/fresh-hook-reimport-test.js +53 -53
- package/_site/_test/fresh-product-data-test.js +56 -56
- package/_site/_test/full-integration-e2e-test.js +167 -167
- package/_site/_test/git-path-compatibility-test.js +80 -80
- package/_site/_test/git-validation-test.js +81 -81
- package/_site/_test/glm-ui-refresh-review-test.js +313 -404
- package/_site/_test/harness-isolation-test.js +138 -0
- package/_site/_test/helpers/cdp-browser.js +383 -0
- package/_site/_test/helpers/cdp-network-capture.js +66 -0
- package/_site/_test/helpers/find-chrome.js +34 -0
- package/_site/_test/helpers/free-port.js +8 -0
- package/_site/_test/helpers/logging-ui-fixture.js +131 -0
- package/_site/_test/helpers/module-fixture.js +282 -0
- package/_site/_test/helpers/parallel-engine-advanced-engine-child.js +45 -0
- package/_site/_test/helpers/parallel-engine-advanced-fault-writer.js +171 -0
- package/_site/_test/helpers/parallel-engine-advanced-harness.js +245 -0
- package/_site/_test/helpers/parallel-engine-advanced-standin-analyzer.js +80 -0
- package/_site/_test/helpers/parallel-engine-advanced-ui-host.js +177 -0
- package/_site/_test/helpers/parallel-engine-final-harness.js +314 -0
- package/_site/_test/helpers/parallel-engine-final-standin-analyzer.js +159 -0
- package/_site/_test/helpers/parallel-knowledge-fixture.js +255 -0
- package/_site/_test/helpers/parallel-logging-acceptance-fixture.js +111 -0
- package/_site/_test/helpers/parallel-logging-acceptance-proxy.js +106 -0
- package/_site/_test/helpers/parallel-logging-fix-r2-proxy.js +205 -0
- package/_site/_test/helpers/parallel-logging-integration-5025-env.js +432 -0
- package/_site/_test/helpers/parallel-memory-analyzer.js +81 -0
- package/_site/_test/helpers/parallel-memory-event-bridge.js +128 -0
- package/_site/_test/helpers/parallel-memory-shell.js +470 -0
- package/_site/_test/helpers/parallel-nav-isolation-env.js +389 -0
- package/_site/_test/helpers/parallel-nav-module-ui.js +186 -0
- package/_site/_test/helpers/parallel-nav-proxy.js +148 -0
- package/_site/_test/helpers/parallel-nav-shell.js +361 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-guard.js +206 -0
- package/_site/_test/helpers/parallel-qa-engine-endpoint-isolate.js +257 -0
- package/_site/_test/helpers/parallel-retrieval-followup-corpus.js +293 -0
- package/_site/_test/helpers/parallel-retrieval-followup-next-session.js +70 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-shell.js +737 -0
- package/_site/_test/helpers/parallel-retrieval-followup-r2-wb-host.js +82 -0
- package/_site/_test/helpers/parallel-retrieval-followup-shell.js +279 -0
- package/_site/_test/helpers/parallel-workbench-fixture.js +371 -0
- package/_site/_test/helpers/parallel-workbench-host.js +80 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-fixture.js +408 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-harness.js +593 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-host.js +70 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-launcher.js +240 -0
- package/_site/_test/helpers/parallel-workbench-integration-5025-runtime-harness.js +630 -0
- package/_site/_test/helpers/parallel-workbench-source-map.js +196 -0
- package/_site/_test/helpers/spawn-server.js +106 -0
- package/_site/_test/helpers/version-visibility-fixture.js +251 -0
- package/_site/_test/hook-runtime-endpoint-test.js +88 -88
- package/_site/_test/hook-status-repair-api-test.js +152 -152
- package/_site/_test/hook-trigger-test.js +85 -85
- package/_site/_test/hook-worktree-test.js +42 -42
- package/_site/_test/import-preflight-api-test.js +234 -234
- package/_site/_test/index-builder-test.js +41 -41
- package/_site/_test/index-error-display-contract-test.js +10 -10
- package/_site/_test/index-writer-concurrency-test.js +135 -135
- package/_site/_test/integration-adapters-test.js +262 -262
- package/_site/_test/integration-auto-repair-test.js +113 -113
- package/_site/_test/integration-capture-setup-test.js +118 -118
- package/_site/_test/integration-intent-test.js +81 -81
- package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
- package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
- package/_site/_test/integration-runtime-binding-test.js +473 -473
- package/_site/_test/integration-status-scan-test.js +29 -29
- package/_site/_test/integration-surface-coverage-test.js +164 -164
- package/_site/_test/kb-framework-test.js +57 -57
- package/_site/_test/kbpath-follow-test.js +63 -63
- package/_site/_test/knowledge-db-test.js +144 -144
- package/_site/_test/knowledge-engine-client-test.js +120 -109
- package/_site/_test/knowledge-language-control-test.js +170 -170
- package/_site/_test/knowledge-maintenance-test.js +40 -40
- package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
- package/_site/_test/knowledge-query-test.js +54 -54
- package/_site/_test/knowledge-retrieval-service-test.js +77 -77
- package/_site/_test/knowledge-scopes-test.js +37 -37
- package/_site/_test/knowledge-storage-location-test.js +28 -28
- package/_site/_test/knowledge-storage-startup-test.js +52 -52
- package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
- package/_site/_test/launcher-readiness-test.js +80 -80
- package/_site/_test/legacy-claude-session-alias-test.js +106 -106
- package/_site/_test/legacy-routes-removed-test.js +47 -47
- package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
- package/_site/_test/live-gateway-test.js +48 -48
- package/_site/_test/local-marketplace-test.js +16 -16
- package/_site/_test/log-redaction-test.js +58 -58
- package/_site/_test/logging-api-test.js +89 -89
- package/_site/_test/logging-sse-no-gap-test.js +88 -88
- package/_site/_test/logging-ui-test.js +107 -107
- package/_site/_test/markdown-delta-overlay-test.js +85 -85
- package/_site/_test/markdown-maintenance-api-test.js +75 -75
- package/_site/_test/markdown-maintenance-test.js +11 -11
- package/_site/_test/mcp-server-test.js +149 -149
- package/_site/_test/model-context-windows-test.js +89 -89
- package/_site/_test/module-agent-start-proxy-test.js +65 -65
- package/_site/_test/module-artifact-integrity-test.js +27 -27
- package/_site/_test/module-boundary-test.js +30 -30
- package/_site/_test/module-bridge-eventbridge-test.js +202 -202
- package/_site/_test/module-dynamic-port-test.js +118 -118
- package/_site/_test/module-index-proxy-test.js +33 -33
- package/_site/_test/module-model-configuration-test.js +44 -44
- package/_site/_test/module-process-lifecycle-test.js +35 -35
- package/_site/_test/module-stream-proxy-test.js +34 -34
- package/_site/_test/module-terminal-restart-test.js +63 -63
- package/_site/_test/module-wiring-unbind-race-test.js +59 -59
- package/_site/_test/native-context-integrations-test.js +86 -86
- package/_site/_test/non-release-ci-test.js +34 -34
- package/_site/_test/offline-boundary-isolation-test.js +32 -32
- package/_site/_test/orphaned-claim-recovery-test.js +80 -80
- package/_site/_test/package-boundary-test.js +43 -43
- package/_site/_test/package-startup-test.js +56 -56
- package/_site/_test/packaged-ui-smoke-test.js +88 -88
- package/_site/_test/parallel-ci-r9-posix-socket-budget-test.js +156 -0
- package/_site/_test/parallel-engine-advanced-eng005-test.js +136 -0
- package/_site/_test/parallel-engine-advanced-eng006-test.js +171 -0
- package/_site/_test/parallel-engine-advanced-eng007-reconcile-test.js +231 -0
- package/_site/_test/parallel-engine-advanced-eng009-test.js +208 -0
- package/_site/_test/parallel-engine-advanced-eng010-test.js +213 -0
- package/_site/_test/parallel-engine-advanced-eng011-ui-probe.js +459 -0
- package/_site/_test/parallel-engine-final-eng002-test.js +255 -0
- package/_site/_test/parallel-engine-final-eng007-test.js +269 -0
- package/_site/_test/parallel-engine-final-eng011-test.js +197 -0
- package/_site/_test/parallel-engine-final-eng012-test.js +354 -0
- package/_site/_test/parallel-knowledge-client-error-code-test.js +184 -0
- package/_site/_test/parallel-knowledge-engine-test.js +353 -0
- package/_site/_test/parallel-knowledge-retrieval-test.js +292 -0
- package/_site/_test/parallel-knowledge-vector-test.js +168 -0
- package/_site/_test/parallel-linux-ci-r7-log007-day-test.js +110 -0
- package/_site/_test/parallel-logging-acceptance-log003-test.js +224 -0
- package/_site/_test/parallel-logging-acceptance-log004-test.js +231 -0
- package/_site/_test/parallel-logging-acceptance-log005-test.js +208 -0
- package/_site/_test/parallel-logging-acceptance-log006-test.js +214 -0
- package/_site/_test/parallel-logging-acceptance-log007-test.js +223 -0
- package/_site/_test/parallel-logging-acceptance-log008-test.js +180 -0
- package/_site/_test/parallel-logging-fix-r2-crash-probe.js +271 -0
- package/_site/_test/parallel-logging-fix-r2-gap-test.js +294 -0
- package/_site/_test/parallel-logging-fix-r2-normalhook-test.js +330 -0
- package/_site/_test/parallel-logging-integration-5025-portability-test.js +554 -0
- package/_site/_test/parallel-memory-candidate-test.js +301 -0
- package/_site/_test/parallel-memory-manual-test.js +197 -0
- package/_site/_test/parallel-memory-scope-test.js +228 -0
- package/_site/_test/parallel-memory-source-test.js +227 -0
- package/_site/_test/parallel-memory-sync-test.js +238 -0
- package/_site/_test/parallel-memory-view-r8-reload-gate-test.js +123 -0
- package/_site/_test/parallel-nav-failure-test.js +119 -0
- package/_site/_test/parallel-nav-isolation-test.js +308 -0
- package/_site/_test/parallel-nav-isolation-vector-path-test.js +90 -0
- package/_site/_test/parallel-nav-offline-test.js +272 -0
- package/_site/_test/parallel-nav-operability-test.js +257 -0
- package/_site/_test/parallel-nav-switch-test.js +247 -0
- package/_site/_test/parallel-nav-theme-test.js +130 -0
- package/_site/_test/parallel-nav-viewport-test.js +310 -0
- package/_site/_test/parallel-qa-engine-endpoint-guard-test.js +273 -0
- package/_site/_test/parallel-retrieval-followup-citation-test.js +219 -0
- package/_site/_test/parallel-retrieval-followup-corpus-test.js +287 -0
- package/_site/_test/parallel-workbench-independent-host.js +262 -0
- package/_site/_test/parallel-workbench-integration-5025-abort-probe.js +340 -0
- package/_site/_test/parallel-workbench-integration-5025-drafts-probe.js +588 -0
- package/_site/_test/parallel-workbench-integration-5025-env-check.js +317 -0
- package/_site/_test/parallel-workbench-integration-5025-model-probe.js +342 -0
- package/_site/_test/parallel-workbench-integration-5025-ordering-probe.js +326 -0
- package/_site/_test/parallel-workbench-model-probe.js +336 -0
- package/_site/_test/parallel-workbench-offline-test.js +169 -0
- package/_site/_test/parallel-workbench-provenance-trace.js +134 -0
- package/_site/_test/parallel-workbench-source-match.js +206 -0
- package/_site/_test/parallel-workbench-standalone-turn.js +349 -0
- package/_site/_test/path-consistency-test.js +151 -151
- package/_site/_test/pending-sweep-test.js +9 -9
- package/_site/_test/post-commit-automation-test.js +87 -87
- package/_site/_test/pr-consumer-contract-test.js +55 -55
- package/_site/_test/product-brand-contract-test.js +25 -25
- package/_site/_test/product-diagnostics-ui-test.js +93 -93
- package/_site/_test/product-empty-state-test.js +148 -0
- package/_site/_test/product-import-ui-test.js +219 -104
- package/_site/_test/product-navigation-test.js +152 -0
- package/_site/_test/product-progress-status-test.js +84 -84
- package/_site/_test/project-delete-recovery-test.js +64 -64
- package/_site/_test/project-domain-create-test.js +91 -0
- package/_site/_test/project-domain-scope-retrieval-test.js +214 -0
- package/_site/_test/project-git-init-confirm-test.js +115 -0
- package/_site/_test/project-goal-editor-test.js +144 -144
- package/_site/_test/project-import-duplicate-alias-test.js +134 -0
- package/_site/_test/project-import-unicode-restart-test.js +140 -0
- package/_site/_test/project-lifecycle-transaction-test.js +106 -106
- package/_site/_test/project-module-partial-recovery-test.js +222 -0
- package/_site/_test/project-remove-running-guard-test.js +79 -79
- package/_site/_test/project-store-test.js +90 -90
- package/_site/_test/project-unbind-late-read-ui-test.js +250 -0
- package/_site/_test/project-unbind-writer-guard-test.js +231 -0
- package/_site/_test/prompt-settings-test.js +115 -115
- package/_site/_test/protected-architecture-gate-test.js +122 -122
- package/_site/_test/refactor-characterization-test.js +36 -36
- package/_site/_test/registry-events-test.js +38 -38
- package/_site/_test/release-version-sync-test.js +115 -115
- package/_site/_test/requirement-binding-test.js +113 -113
- package/_site/_test/requirement-recorder-test.js +173 -173
- package/_site/_test/run-all-tests.js +173 -161
- package/_site/_test/runtime-endpoint-test.js +64 -64
- package/_site/_test/scanner-test.js +83 -83
- package/_site/_test/server-security-test.js +96 -96
- package/_site/_test/sessions-stream-test.js +93 -93
- package/_site/_test/shared-contracts-test.js +58 -58
- package/_site/_test/shared-scope-root-binding-test.js +74 -67
- package/_site/_test/simple-import-test.js +98 -98
- package/_site/_test/startup-analysis-disabled-test.js +16 -16
- package/_site/_test/storage-foundation-test.js +78 -78
- package/_site/_test/structured-logger-test.js +101 -101
- package/_site/_test/t07b-cln-005-coverage-test.js +212 -0
- package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
- package/_site/_test/team-knowledge-store-test.js +132 -132
- package/_site/_test/test-server-cleanup-test.js +35 -35
- package/_site/_test/test-server-module-isolation-test.js +37 -37
- package/_site/_test/tracking-start-test.js +144 -144
- package/_site/_test/vector-storage-root-test.js +75 -75
- package/_site/_test/version-visibility-browser-probe.js +249 -0
- package/_site/_test/version-visibility-test.js +92 -0
- package/_site/_test/workbench-context-receipts-test.js +22 -22
- package/_site/_test/workbench-permission-test.js +117 -117
- package/_site/_test/zcode-capture-integration-test.js +112 -112
- package/_site/lib/ai-profile-resolver.js +78 -78
- package/_site/lib/ai-vendor-presets.js +83 -83
- package/_site/lib/ai-workspace.js +104 -104
- package/_site/lib/atomic-file.js +204 -204
- package/_site/lib/automation-config.js +140 -140
- package/_site/lib/bridge-adapter.js +260 -260
- package/_site/lib/bridge-consumer-service.js +402 -402
- package/_site/lib/capture-hook-config.js +108 -108
- package/_site/lib/claude-cli-runner.js +1599 -1599
- package/_site/lib/cli-instances.js +43 -43
- package/_site/lib/codex-context-status.js +17 -17
- package/_site/lib/codex-notify-config.js +193 -193
- package/_site/lib/commit-conversation-binder.js +247 -247
- package/_site/lib/commit-processing-ledger.js +27 -27
- package/_site/lib/commit-prompt.js +202 -202
- package/_site/lib/commit-reconciler.js +444 -444
- package/_site/lib/context-pack-builder.js +299 -299
- package/_site/lib/contracts.js +291 -291
- package/_site/lib/conversation-exclusions.js +204 -204
- package/_site/lib/conversation-query-service.js +161 -161
- package/_site/lib/conversation-store.js +387 -387
- package/_site/lib/data-dir.js +13 -13
- package/_site/lib/embedding-service.js +85 -85
- package/_site/lib/engine-connection.js +93 -93
- package/_site/lib/engine-knowledge-client.js +76 -72
- package/_site/lib/evidence-bundle.js +228 -228
- package/_site/lib/folder-picker-output.js +24 -24
- package/_site/lib/git-path.js +14 -14
- package/_site/lib/git-runner.js +62 -62
- package/_site/lib/github-team-store.js +1175 -1175
- package/_site/lib/hook-manager.js +203 -203
- package/_site/lib/index-builder.js +157 -157
- package/_site/lib/index-service.js +173 -173
- package/_site/lib/integration-auto-repair.js +77 -77
- package/_site/lib/integration-installer.js +1240 -1240
- package/_site/lib/integration-intent.js +88 -88
- package/_site/lib/integration-status-scan.js +99 -99
- package/_site/lib/integration-status-worker.js +14 -14
- package/_site/lib/kb-framework.js +191 -191
- package/_site/lib/kb-validator.js +124 -124
- package/_site/lib/knowledge-db.js +363 -363
- package/_site/lib/knowledge-promotion.js +435 -435
- package/_site/lib/knowledge-retrieval-service.js +401 -401
- package/_site/lib/knowledge-schema.js +121 -121
- package/_site/lib/knowledge-tool-runtime.js +366 -366
- package/_site/lib/live-gateway.js +78 -78
- package/_site/lib/llm-client.js +173 -173
- package/_site/lib/markdown-knowledge-indexer.js +230 -230
- package/_site/lib/model-context-windows.js +82 -82
- package/_site/lib/module-bridge.js +874 -874
- package/_site/lib/module-port-resolver.js +57 -57
- package/_site/lib/post-commit-automation.js +141 -141
- package/_site/lib/product-data.js +35 -35
- package/_site/lib/project-lifecycle-service.js +554 -554
- package/_site/lib/project-registry-store.js +129 -129
- package/_site/lib/project-store.js +268 -268
- package/_site/lib/prompt-registry.js +148 -148
- package/_site/lib/public-version-info.js +37 -0
- package/_site/lib/registry-events.js +16 -16
- package/_site/lib/requirement-adapters.js +10 -10
- package/_site/lib/requirement-binder.js +112 -112
- package/_site/lib/requirement-recorder.js +278 -278
- package/_site/lib/runtime-control.js +89 -89
- package/_site/lib/runtime-endpoint.js +160 -160
- package/_site/lib/scanner.js +298 -298
- package/_site/lib/server-app.js +1672 -1664
- package/_site/lib/settings-store.js +153 -153
- package/_site/lib/storage-layout.js +166 -166
- package/_site/lib/structured-logger.js +574 -574
- package/_site/lib/zcode-context-integration.js +60 -60
- package/_site/scripts/codex-notify-fanout.cjs +95 -95
- package/_site/scripts/folder-picker.ps1 +156 -156
- package/_site/scripts/hook-trigger.js +158 -158
- package/_site/scripts/install-module-candidates.js +69 -69
- package/_site/scripts/pack-module-candidates.js +89 -89
- package/_site/scripts/sync-product-content.js +28 -28
- package/_site/scripts/sync-release-version.js +164 -164
- package/_site/scripts/vendor-modules.js +49 -49
- package/_site/scripts/verify-module-runtime.js +50 -50
- package/_site/scripts/verify-product-runtime.js +36 -36
- package/_site/scripts/verify-retrieval-gold.js +83 -83
- package/_site/server.js +15 -15
- package/_site/start.bat +26 -26
- package/_site/stop.bat +11 -11
- package/bin/project-knowledge-integrations.js +254 -254
- package/bin/project-knowledge-kb.js +67 -67
- package/bin/project-knowledge-mcp.js +201 -201
- package/bin/project-knowledge.js +269 -170
- package/claude-prompts.json +21 -21
- package/docs/README.zh-CN.md +207 -205
- package/docs/assets/architecture.svg +181 -181
- package/docs/assets/logo.svg +49 -49
- package/docs/fresh-v5-start.md +44 -44
- package/docs/leader-glm-workbench-playbook.md +122 -122
- package/docs/project-registry-schema.md +33 -33
- package/docs/release-5.0.16-acceptance.md +176 -176
- package/docs/release-5.0.17-acceptance.md +117 -117
- package/docs/release-5.0.19-acceptance.md +39 -39
- package/docs/team-knowledge-mode-a-plan.md +73 -73
- package/docs/testing-strategy.md +33 -33
- package/module-runtime-manifest.json +66 -63
- package/package.json +113 -110
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/templates/change.md +30 -30
- package/templates/project-readme.md +52 -52
- package/ui/index.html +3 -2
- package/ui/version-info.css +44 -0
- package/ui/version-info.js +24 -0
- package/vendor-manifest.json +55 -55
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// F-ENV-LEAK-001 canonical regression: a unit test and the suite runner are
|
|
3
|
+
// decided by their own fixtures, never by ambient module URLs or an ambient
|
|
4
|
+
// KB_KNOWLEDGE_DATA_DIR inherited from the developer shell. The read-only
|
|
5
|
+
// sentinel below stands in for the user's live instance: it records every
|
|
6
|
+
// request, so a leak is observable WITHOUT ever contacting a real instance,
|
|
7
|
+
// and it refuses every binding as foreign — exactly the protective 409 the
|
|
8
|
+
// real instance gave, which must surface, never be swallowed.
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const os = require('node:os');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const http = require('node:http');
|
|
14
|
+
const { spawn } = require('node:child_process');
|
|
15
|
+
const { StorageLayout } = require('../lib/storage-layout');
|
|
16
|
+
const { EngineKnowledgeClient } = require('../lib/engine-knowledge-client');
|
|
17
|
+
|
|
18
|
+
const MODULE_URL_VARS = ['KB_KNOWLEDGE_ENGINE_URL', 'KB_VECTORHUB_URL', 'KB_TERMINAL_URL', 'KB_EVENTBRIDGE_URL'];
|
|
19
|
+
const REPO = path.resolve(__dirname, '..', '..');
|
|
20
|
+
|
|
21
|
+
// The sentinel lives in THIS process, so children must be spawned
|
|
22
|
+
// asynchronously: a synchronous spawn would freeze the event loop and the
|
|
23
|
+
// sentinel could never answer (the leak-probe child would just time out).
|
|
24
|
+
function run(file, args, options, timeoutMs) {
|
|
25
|
+
return new Promise(resolve => {
|
|
26
|
+
const child = spawn(file, args, { ...options, windowsHide: true });
|
|
27
|
+
let out = '', err = '';
|
|
28
|
+
child.stdout.on('data', b => { out += b; }); child.stderr.on('data', b => { err += b; });
|
|
29
|
+
const timer = setTimeout(() => { child.kill(); resolve({ status: null, timedOut: true, out, err }); }, timeoutMs);
|
|
30
|
+
child.once('error', error => { clearTimeout(timer); resolve({ status: null, error, out, err }); });
|
|
31
|
+
child.once('close', status => { clearTimeout(timer); resolve({ status, out, err }); });
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
(async () => {
|
|
36
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-isolation-'));
|
|
37
|
+
const sentinelHits = [];
|
|
38
|
+
const sentinel = http.createServer((req, res) => {
|
|
39
|
+
sentinelHits.push(req.url);
|
|
40
|
+
res.setHeader('Content-Type', 'application/json');
|
|
41
|
+
if (req.url === '/api/health') return res.end(JSON.stringify({ name: 'knowledge-engine', dataSchemaVersion: 3 }));
|
|
42
|
+
if (req.url === '/api/projects') return res.end(JSON.stringify({ projects: [{ projectId: 'foreign', repoPath: path.join(root, 'foreign-repo'), knowledgePath: path.join(root, 'foreign-knowledge'), enabled: true }] }));
|
|
43
|
+
res.statusCode = 409;
|
|
44
|
+
res.end(JSON.stringify({ ok: false, error: { code: 'ENGINE_IDENTITY_MISMATCH' } }));
|
|
45
|
+
});
|
|
46
|
+
await new Promise(resolve => sentinel.listen(0, '127.0.0.1', resolve));
|
|
47
|
+
const sentinelUrl = `http://127.0.0.1:${sentinel.address().port}`;
|
|
48
|
+
const decoySite = path.join(root, 'decoy-site-data'), decoyKnowledge = path.join(root, 'decoy-knowledge-data');
|
|
49
|
+
fs.mkdirSync(decoySite); fs.mkdirSync(decoyKnowledge);
|
|
50
|
+
const polluted = {
|
|
51
|
+
...process.env,
|
|
52
|
+
KB_KNOWLEDGE_ENGINE_URL: sentinelUrl, KB_VECTORHUB_URL: sentinelUrl, KB_TERMINAL_URL: sentinelUrl, KB_EVENTBRIDGE_URL: sentinelUrl,
|
|
53
|
+
KB_DATA_DIR: decoySite, KB_KNOWLEDGE_DATA_DIR: decoyKnowledge,
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
// 1. Detector control: a client built WITHOUT an explicit engineUrl
|
|
57
|
+
// under the polluted env does reach the ambient address, and the
|
|
58
|
+
// identity guard refuses the foreign binding out loud.
|
|
59
|
+
const leakProbe = path.join(root, 'leak-probe.js');
|
|
60
|
+
fs.writeFileSync(leakProbe, `'use strict';
|
|
61
|
+
const { StorageLayout } = require(${JSON.stringify(path.resolve(__dirname, '../lib/storage-layout'))});
|
|
62
|
+
const { EngineKnowledgeClient } = require(${JSON.stringify(path.resolve(__dirname, '../lib/engine-knowledge-client'))});
|
|
63
|
+
(async () => {
|
|
64
|
+
const layout = new StorageLayout({ dataDir: process.env.KB_DATA_DIR });
|
|
65
|
+
const client = new EngineKnowledgeClient({ layout });
|
|
66
|
+
try {
|
|
67
|
+
await client.request('search', { query: 'probe' }, { projectId: 'foreign', repoPath: process.env.KB_DATA_DIR, knowledgePath: process.env.KB_DATA_DIR });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error.code !== 'ENGINE_IDENTITY_MISMATCH' || error.status !== 409) throw error;
|
|
70
|
+
console.log('LEAK_REJECTED_ENGINE_IDENTITY_MISMATCH');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
throw new Error('the sentinel must refuse the foreign binding');
|
|
74
|
+
})().catch(error => { console.error(error); process.exit(1); });
|
|
75
|
+
`);
|
|
76
|
+
const leak = await run(process.execPath, [leakProbe], { env: polluted }, 30000);
|
|
77
|
+
assert.equal(leak.status, 0, `detector control must fail loudly if broken: ${leak.err}`);
|
|
78
|
+
assert.match(leak.out, /LEAK_REJECTED_ENGINE_IDENTITY_MISMATCH/);
|
|
79
|
+
assert.ok(sentinelHits.length >= 1, 'the detector control must observe the leaked request');
|
|
80
|
+
const hitsAfterControl = sentinelHits.length;
|
|
81
|
+
|
|
82
|
+
// 2. The fixed canonical test under the same polluted env passes and
|
|
83
|
+
// sends not a single request to the ambient address.
|
|
84
|
+
const canonical = await run(process.execPath, [path.resolve(__dirname, 'knowledge-engine-client-test.js')], { env: polluted }, 120000);
|
|
85
|
+
assert.equal(canonical.status, 0, `canonical test must pass under a polluted environment: ${canonical.err}`);
|
|
86
|
+
assert.equal(sentinelHits.length, hitsAfterControl, 'the canonical test must not contact an ambient module address');
|
|
87
|
+
|
|
88
|
+
// 3. The runner scrubs its children: exclusive probe dir with the real
|
|
89
|
+
// (fixed) run-all-tests.js and one env-dump test file. The child of
|
|
90
|
+
// the runner must see no ambient module URL, must NOT see the decoy
|
|
91
|
+
// KB data dirs, and must get its own sandboxes instead.
|
|
92
|
+
const probeOut = path.join(root, 'probe-env.json');
|
|
93
|
+
const runnerDir = path.join(root, 'runner-probe', '_site', '_test');
|
|
94
|
+
fs.mkdirSync(runnerDir, { recursive: true });
|
|
95
|
+
fs.copyFileSync(path.resolve(__dirname, 'run-all-tests.js'), path.join(runnerDir, 'run-all-tests.js'));
|
|
96
|
+
fs.writeFileSync(path.join(runnerDir, 'zz-env-probe-test.js'), `'use strict';
|
|
97
|
+
const fs = require('node:fs');
|
|
98
|
+
fs.writeFileSync(process.env.PROBE_OUT, JSON.stringify({
|
|
99
|
+
${MODULE_URL_VARS.map(name => `${name}: process.env.${name}`).join(', ')},
|
|
100
|
+
KB_DATA_DIR: process.env.KB_DATA_DIR,
|
|
101
|
+
KB_KNOWLEDGE_DATA_DIR: process.env.KB_KNOWLEDGE_DATA_DIR,
|
|
102
|
+
AI_CODING_EVENT_BRIDGE_HOME: process.env.AI_CODING_EVENT_BRIDGE_HOME,
|
|
103
|
+
KB_MODULES_AUTOSTART: process.env.KB_MODULES_AUTOSTART,
|
|
104
|
+
}));
|
|
105
|
+
`);
|
|
106
|
+
const runner = await run(process.execPath, [path.join(runnerDir, 'run-all-tests.js'), '--no-report'], {
|
|
107
|
+
cwd: REPO, env: { ...polluted, PROBE_OUT: probeOut },
|
|
108
|
+
}, 120000);
|
|
109
|
+
assert.equal(runner.status, 0, `runner probe must pass: ${runner.err}`);
|
|
110
|
+
assert.match(runner.out, /1 passed, 0 failed/);
|
|
111
|
+
const seen = JSON.parse(fs.readFileSync(probeOut, 'utf8'));
|
|
112
|
+
for (const name of MODULE_URL_VARS) assert.equal(seen[name], undefined, `runner children must not inherit ${name}`);
|
|
113
|
+
assert.notEqual(seen.KB_DATA_DIR, decoySite, 'runner children must not inherit the ambient site data dir');
|
|
114
|
+
assert.notEqual(seen.KB_KNOWLEDGE_DATA_DIR, decoyKnowledge, 'runner children must not inherit the ambient knowledge data dir');
|
|
115
|
+
assert.ok(seen.KB_DATA_DIR && seen.KB_DATA_DIR.includes('kb-runall-data-'), 'site data must be a per-run sandbox');
|
|
116
|
+
assert.ok(seen.KB_KNOWLEDGE_DATA_DIR && seen.KB_KNOWLEDGE_DATA_DIR.includes('kb-runall-knowledge-'), 'knowledge data must be a per-run sandbox');
|
|
117
|
+
assert.ok(seen.AI_CODING_EVENT_BRIDGE_HOME, 'bridge home must stay sandboxed');
|
|
118
|
+
assert.equal(seen.KB_MODULES_AUTOSTART, '0', 'module autostart must stay disabled');
|
|
119
|
+
assert.equal(sentinelHits.length, hitsAfterControl, 'the runner child must not contact an ambient module address');
|
|
120
|
+
|
|
121
|
+
// 4. Deliberate configuration is untouched: an explicit engineUrl still
|
|
122
|
+
// aims the client at the chosen address — proven by the sentinel hits
|
|
123
|
+
// this final control produces on purpose.
|
|
124
|
+
const layout = new StorageLayout({ dataDir: path.join(root, 'site-data') });
|
|
125
|
+
const deliberate = new EngineKnowledgeClient({ layout, engineUrl: sentinelUrl });
|
|
126
|
+
await assert.rejects(
|
|
127
|
+
deliberate.request('search', { query: 'probe' }, { projectId: 'foreign', repoPath: path.join(root, 'site-repo'), knowledgePath: path.join(root, 'site-knowledge') }),
|
|
128
|
+
error => error.code === 'ENGINE_IDENTITY_MISMATCH' && error.status === 409,
|
|
129
|
+
'an explicitly configured client still gets the identity refusal surfaced, never swallowed',
|
|
130
|
+
);
|
|
131
|
+
assert.ok(sentinelHits.length > hitsAfterControl, 'the deliberate configuration must actually reach its target');
|
|
132
|
+
console.log('harness-isolation-test PASS (ambient module URLs and data dirs cannot decide tests; deliberate config and identity refusals stay loud)');
|
|
133
|
+
} finally {
|
|
134
|
+
sentinel.closeAllConnections();
|
|
135
|
+
await new Promise(resolve => sentinel.close(resolve));
|
|
136
|
+
fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
137
|
+
}
|
|
138
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { spawn, spawnSync } = require('child_process');
|
|
5
|
+
const WebSocket = require('ws');
|
|
6
|
+
|
|
7
|
+
const MAX_STREAM_TAIL_BYTES = 32 * 1024;
|
|
8
|
+
const DEFAULT_HTTP_TIMEOUT_MS = 1000;
|
|
9
|
+
// Hosted-runner cold starts (first Chrome launch of a suite, antivirus scan,
|
|
10
|
+
// dbus stalls) can exceed a single budget window; launch stalls are transient,
|
|
11
|
+
// so retry instead of failing the whole test run.
|
|
12
|
+
const DEFAULT_LAUNCH_TIMEOUT_MS = Number(process.env.KB_CDP_LAUNCH_TIMEOUT_MS || 30000);
|
|
13
|
+
const DEFAULT_LAUNCH_ATTEMPTS = Math.max(1, Number(process.env.KB_CDP_LAUNCH_ATTEMPTS || 3));
|
|
14
|
+
|
|
15
|
+
function wait(ms) {
|
|
16
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function waitFor(fn, label, timeoutMs = 15000) {
|
|
20
|
+
const deadline = Date.now() + timeoutMs;
|
|
21
|
+
let lastError;
|
|
22
|
+
while (Date.now() < deadline) {
|
|
23
|
+
try {
|
|
24
|
+
const result = await fn();
|
|
25
|
+
if (result) return result;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error && error.fatal) throw error;
|
|
28
|
+
lastError = error;
|
|
29
|
+
}
|
|
30
|
+
await wait(100);
|
|
31
|
+
}
|
|
32
|
+
throw new Error("Timed out waiting for " + label + (lastError ? ": " + lastError.message : ""));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestJson(url, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
let settled = false;
|
|
38
|
+
const request = http.get(url, response => {
|
|
39
|
+
let body = "";
|
|
40
|
+
response.setEncoding("utf8");
|
|
41
|
+
response.on("data", chunk => { body += chunk; });
|
|
42
|
+
response.on("end", () => {
|
|
43
|
+
if (settled) return;
|
|
44
|
+
settled = true;
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
try { resolve(JSON.parse(body)); }
|
|
47
|
+
catch (error) { reject(error); }
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
if (settled) return;
|
|
52
|
+
settled = true;
|
|
53
|
+
const error = new Error("HTTP request timed out after " + timeoutMs + "ms: " + url);
|
|
54
|
+
error.code = "ETIMEDOUT";
|
|
55
|
+
request.destroy(error);
|
|
56
|
+
reject(error);
|
|
57
|
+
}, timeoutMs);
|
|
58
|
+
request.on("error", error => {
|
|
59
|
+
if (settled) return;
|
|
60
|
+
settled = true;
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
reject(error);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createBoundedTail(maxBytes = MAX_STREAM_TAIL_BYTES) {
|
|
68
|
+
let buffer = Buffer.alloc(0);
|
|
69
|
+
return {
|
|
70
|
+
push(chunk) {
|
|
71
|
+
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
72
|
+
if (buffer.length + data.length <= maxBytes) {
|
|
73
|
+
buffer = Buffer.concat([buffer, data]);
|
|
74
|
+
} else {
|
|
75
|
+
buffer = Buffer.concat([buffer.slice(buffer.length + data.length - maxBytes), data]);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
text() { return buffer.toString("utf8"); },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasExited(child) {
|
|
83
|
+
return !child || child.exitCode !== null || child.signalCode !== null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isWindowsChromeLauncher(executable) {
|
|
87
|
+
if (process.platform !== "win32") return false;
|
|
88
|
+
return /(?:chrome|msedge)\.exe$/i.test(path.basename(String(executable || "")));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function platformLaunchArgs(platform = process.platform) {
|
|
92
|
+
// GitHub Linux runners occasionally leave Chrome alive without publishing
|
|
93
|
+
// DevToolsActivePort when its sandbox or shared-memory backing stalls.
|
|
94
|
+
// These flags are scoped to isolated, disposable test browsers only.
|
|
95
|
+
return platform === 'linux' ? ['--disable-dev-shm-usage', '--no-sandbox'] : [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function waitForExit(child, timeoutMs = 3000) {
|
|
99
|
+
if (hasExited(child)) return Promise.resolve();
|
|
100
|
+
return new Promise(resolve => {
|
|
101
|
+
const finish = () => { clearTimeout(timer); resolve(); };
|
|
102
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
103
|
+
child.once("exit", finish);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function terminateProfileProcesses(profileDir) {
|
|
108
|
+
if (process.platform !== "win32" || !profileDir) return;
|
|
109
|
+
const command = "$profile = $env:PK_CDP_PROFILE; "
|
|
110
|
+
+ "Get-CimInstance Win32_Process -Filter \"Name = 'chrome.exe' OR Name = 'msedge.exe'\" "
|
|
111
|
+
+ "| Where-Object { $_.CommandLine -and $_.CommandLine.IndexOf($profile, [System.StringComparison]::OrdinalIgnoreCase) -ge 0 } "
|
|
112
|
+
+ "| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }";
|
|
113
|
+
try {
|
|
114
|
+
spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
115
|
+
stdio: "ignore",
|
|
116
|
+
timeout: 10000,
|
|
117
|
+
windowsHide: true,
|
|
118
|
+
env: { ...process.env, PK_CDP_PROFILE: profileDir },
|
|
119
|
+
});
|
|
120
|
+
} catch {}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function terminateProcessTree(child, profileDir) {
|
|
124
|
+
if (!hasExited(child) && process.platform === "win32" && child.pid) {
|
|
125
|
+
try {
|
|
126
|
+
spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore", timeout: 10000 });
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
if (!hasExited(child)) {
|
|
130
|
+
try { child.kill(); } catch {}
|
|
131
|
+
await waitForExit(child, 3000);
|
|
132
|
+
}
|
|
133
|
+
// Windows Chrome/Edge can detach from the launcher process. Clean those
|
|
134
|
+
// launchers by their unique profile, but do not pay for a machine-wide CIM
|
|
135
|
+
// scan when a non-browser failure fixture (for example node.exe) exited.
|
|
136
|
+
if (child && isWindowsChromeLauncher(child.spawnfile)) terminateProfileProcesses(profileDir);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function removeProfileDir(profileDir) {
|
|
140
|
+
try {
|
|
141
|
+
fs.rmSync(profileDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
142
|
+
} catch {}
|
|
143
|
+
return !fs.existsSync(profileDir);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function cleanupSpawnedBrowser(child, profileDir) {
|
|
147
|
+
try {
|
|
148
|
+
await terminateProcessTree(child, profileDir);
|
|
149
|
+
} catch {}
|
|
150
|
+
if (profileDir) removeProfileDir(profileDir);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function enrichBrowserLaunchError(error, diagnostics) {
|
|
154
|
+
const parts = ["Failed to launch CDP browser: " + (error && error.message ? error.message : String(error))];
|
|
155
|
+
if (diagnostics.executable) parts.push("executable: " + diagnostics.executable);
|
|
156
|
+
if (diagnostics.pid) parts.push("pid: " + diagnostics.pid);
|
|
157
|
+
if (diagnostics.exitCode !== null && diagnostics.exitCode !== undefined) parts.push("exit code: " + diagnostics.exitCode);
|
|
158
|
+
if (diagnostics.signal) parts.push("signal: " + diagnostics.signal);
|
|
159
|
+
if (diagnostics.spawnError) parts.push("spawn error: " + diagnostics.spawnError);
|
|
160
|
+
if (diagnostics.profileDir) parts.push("user-data-dir: " + diagnostics.profileDir);
|
|
161
|
+
const stderrTail = String(diagnostics.stderrTail || "").trim();
|
|
162
|
+
const stdoutTail = String(diagnostics.stdoutTail || "").trim();
|
|
163
|
+
if (stderrTail) parts.push("stderr tail:\n" + stderrTail);
|
|
164
|
+
if (stdoutTail) parts.push("stdout tail:\n" + stdoutTail);
|
|
165
|
+
const enriched = new Error(parts.filter(Boolean).join("\n"));
|
|
166
|
+
if (error) {
|
|
167
|
+
enriched.cause = error;
|
|
168
|
+
if (error.code) enriched.code = error.code;
|
|
169
|
+
if (error.fatal) enriched.fatal = true;
|
|
170
|
+
}
|
|
171
|
+
enriched.diagnostics = diagnostics;
|
|
172
|
+
return enriched;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function describeEarlyExit(exitInfo, spawnError) {
|
|
176
|
+
if (spawnError) return "browser process failed to start: " + spawnError;
|
|
177
|
+
const code = exitInfo && exitInfo.code !== null && exitInfo.code !== undefined ? String(exitInfo.code) : "unknown";
|
|
178
|
+
const signal = exitInfo && exitInfo.signal ? " (signal " + exitInfo.signal + ")" : "";
|
|
179
|
+
return "browser exited before exposing CDP (exit code " + code + signal + ")";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function waitForDevToolsPort(profileDir, state, timeoutMs, tolerateLauncherExit) {
|
|
183
|
+
const portFile = path.join(profileDir, "DevToolsActivePort");
|
|
184
|
+
return waitFor(() => {
|
|
185
|
+
let text;
|
|
186
|
+
try { text = fs.readFileSync(portFile, "utf8"); } catch {}
|
|
187
|
+
const port = Number.parseInt(String(text || "").split(/\r?\n/, 1)[0], 10);
|
|
188
|
+
if (Number.isInteger(port) && port > 0) return port;
|
|
189
|
+
if (state.exited && !tolerateLauncherExit) {
|
|
190
|
+
const error = new Error(describeEarlyExit(state.exitInfo, state.spawnError));
|
|
191
|
+
error.fatal = true;
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}, "DevToolsActivePort file", timeoutMs);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function launchBrowserAttempt(options) {
|
|
199
|
+
const chrome = options.chrome;
|
|
200
|
+
const profileDir = path.resolve(options.profileDir);
|
|
201
|
+
const launchTimeoutMs = Number(options.launchTimeoutMs || DEFAULT_LAUNCH_TIMEOUT_MS);
|
|
202
|
+
const explicitPort = Number(options.debugPort) > 0 ? Number(options.debugPort) : 0;
|
|
203
|
+
const prependArgs = Array.isArray(options.prependArgs) ? options.prependArgs : [];
|
|
204
|
+
const tolerateLauncherExit = isWindowsChromeLauncher(chrome);
|
|
205
|
+
|
|
206
|
+
removeProfileDir(profileDir);
|
|
207
|
+
fs.mkdirSync(profileDir, { recursive: true });
|
|
208
|
+
|
|
209
|
+
const stdoutTail = createBoundedTail();
|
|
210
|
+
const stderrTail = createBoundedTail();
|
|
211
|
+
const state = { exited: false, exitInfo: null, spawnError: null };
|
|
212
|
+
const diagnostics = {
|
|
213
|
+
executable: chrome,
|
|
214
|
+
pid: null,
|
|
215
|
+
exitCode: null,
|
|
216
|
+
signal: null,
|
|
217
|
+
spawnError: null,
|
|
218
|
+
profileDir,
|
|
219
|
+
stdoutTail: "",
|
|
220
|
+
stderrTail: "",
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
let child = null;
|
|
224
|
+
let socket = null;
|
|
225
|
+
|
|
226
|
+
const failLaunch = async (error) => {
|
|
227
|
+
diagnostics.stdoutTail = stdoutTail.text();
|
|
228
|
+
diagnostics.stderrTail = stderrTail.text();
|
|
229
|
+
if (state.exitInfo) {
|
|
230
|
+
diagnostics.exitCode = state.exitInfo.code;
|
|
231
|
+
diagnostics.signal = state.exitInfo.signal;
|
|
232
|
+
}
|
|
233
|
+
if (state.spawnError) diagnostics.spawnError = state.spawnError;
|
|
234
|
+
if (socket) { try { socket.close(); } catch {} }
|
|
235
|
+
await cleanupSpawnedBrowser(child, profileDir);
|
|
236
|
+
throw enrichBrowserLaunchError(error, diagnostics);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
child = spawn(chrome, [
|
|
241
|
+
...prependArgs,
|
|
242
|
+
...platformLaunchArgs(),
|
|
243
|
+
"--headless=new",
|
|
244
|
+
"--disable-gpu",
|
|
245
|
+
"--disable-background-networking",
|
|
246
|
+
"--disable-default-apps",
|
|
247
|
+
"--disable-extensions",
|
|
248
|
+
"--no-first-run",
|
|
249
|
+
"--no-default-browser-check",
|
|
250
|
+
"--remote-debugging-port=" + explicitPort,
|
|
251
|
+
"--user-data-dir=" + profileDir,
|
|
252
|
+
"--window-size=" + Number(options.width || 1360) + "," + Number(options.height || 900),
|
|
253
|
+
"about:blank",
|
|
254
|
+
], {
|
|
255
|
+
stdio: ["ignore", "pipe", "pipe"], windowsHide: true,
|
|
256
|
+
...(options.env ? { env: options.env } : {}),
|
|
257
|
+
});
|
|
258
|
+
diagnostics.pid = child.pid;
|
|
259
|
+
|
|
260
|
+
child.stdout.on("data", chunk => stdoutTail.push(chunk));
|
|
261
|
+
child.stderr.on("data", chunk => stderrTail.push(chunk));
|
|
262
|
+
child.on("error", error => {
|
|
263
|
+
state.exited = true;
|
|
264
|
+
state.spawnError = error && error.message ? error.message : String(error);
|
|
265
|
+
if (!state.exitInfo) state.exitInfo = { code: null, signal: null };
|
|
266
|
+
});
|
|
267
|
+
child.on("exit", (code, signal) => {
|
|
268
|
+
state.exited = true;
|
|
269
|
+
state.exitInfo = { code, signal };
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const debugPort = explicitPort || await waitForDevToolsPort(profileDir, state, launchTimeoutMs, tolerateLauncherExit);
|
|
273
|
+
|
|
274
|
+
const pages = await waitFor(() => {
|
|
275
|
+
if (state.exited && !tolerateLauncherExit) {
|
|
276
|
+
const error = new Error(describeEarlyExit(state.exitInfo, state.spawnError));
|
|
277
|
+
error.fatal = true;
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
return requestJson("http://127.0.0.1:" + debugPort + "/json").then(items => {
|
|
281
|
+
const available = items.filter(item => item.type === "page");
|
|
282
|
+
return available.length ? available : null;
|
|
283
|
+
});
|
|
284
|
+
}, "Chrome debugging page (port " + debugPort + ")", launchTimeoutMs);
|
|
285
|
+
const page = pages[0];
|
|
286
|
+
socket = new WebSocket(page.webSocketDebuggerUrl);
|
|
287
|
+
await new Promise((resolve, reject) => {
|
|
288
|
+
socket.once("open", resolve);
|
|
289
|
+
socket.once("error", reject);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
let nextId = 1;
|
|
293
|
+
const pending = new Map();
|
|
294
|
+
const exceptions = [];
|
|
295
|
+
const consoleErrors = [];
|
|
296
|
+
socket.on("message", raw => {
|
|
297
|
+
const message = JSON.parse(String(raw));
|
|
298
|
+
if (message.method === "Runtime.exceptionThrown") exceptions.push(message.params);
|
|
299
|
+
if (message.method === "Log.entryAdded" && message.params && message.params.entry && message.params.entry.level === "error") {
|
|
300
|
+
consoleErrors.push(message.params.entry);
|
|
301
|
+
}
|
|
302
|
+
if (!message.id || !pending.has(message.id)) return;
|
|
303
|
+
const callbacks = pending.get(message.id);
|
|
304
|
+
pending.delete(message.id);
|
|
305
|
+
if (message.error) callbacks.reject(new Error(message.error.message));
|
|
306
|
+
else callbacks.resolve(message.result || {});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
function send(method, params) {
|
|
310
|
+
return new Promise((resolve, reject) => {
|
|
311
|
+
const id = nextId++;
|
|
312
|
+
pending.set(id, { resolve, reject });
|
|
313
|
+
socket.send(JSON.stringify({ id, method, params: params || {} }));
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function evaluate(expression) {
|
|
318
|
+
const result = await send("Runtime.evaluate", {
|
|
319
|
+
expression,
|
|
320
|
+
awaitPromise: true,
|
|
321
|
+
returnByValue: true,
|
|
322
|
+
userGesture: true,
|
|
323
|
+
});
|
|
324
|
+
if (result.exceptionDetails) {
|
|
325
|
+
const detail = result.exceptionDetails.exception && result.exceptionDetails.exception.description;
|
|
326
|
+
throw new Error(detail || result.exceptionDetails.text || "Browser evaluation failed");
|
|
327
|
+
}
|
|
328
|
+
return result.result ? result.result.value : undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
await send("Page.enable");
|
|
332
|
+
await send("Runtime.enable");
|
|
333
|
+
await send("Log.enable");
|
|
334
|
+
await send("Page.navigate", { url: options.url });
|
|
335
|
+
await waitFor(() => evaluate("document.readyState === 'complete'"), "page load");
|
|
336
|
+
|
|
337
|
+
async function screenshot(filePath) {
|
|
338
|
+
const result = await send("Page.captureScreenshot", { format: "png", fromSurface: true, captureBeyondViewport: false });
|
|
339
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
340
|
+
fs.writeFileSync(filePath, Buffer.from(result.data, "base64"));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function setViewport(width, height, deviceScaleFactor = 1) {
|
|
344
|
+
await send("Emulation.setDeviceMetricsOverride", {
|
|
345
|
+
width,
|
|
346
|
+
height,
|
|
347
|
+
deviceScaleFactor,
|
|
348
|
+
mobile: width < 620,
|
|
349
|
+
screenWidth: width,
|
|
350
|
+
screenHeight: height,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function close() {
|
|
355
|
+
try { socket.close(); } catch {}
|
|
356
|
+
await terminateProcessTree(child, profileDir);
|
|
357
|
+
removeProfileDir(profileDir);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return { child, socket, send, evaluate, screenshot, setViewport, exceptions, consoleErrors, close };
|
|
361
|
+
} catch (error) {
|
|
362
|
+
await failLaunch(error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function launchCdpBrowser(options) {
|
|
367
|
+
const attempts = Math.max(1, Number(options.launchAttempts || DEFAULT_LAUNCH_ATTEMPTS));
|
|
368
|
+
let lastError;
|
|
369
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
370
|
+
try {
|
|
371
|
+
return await launchBrowserAttempt(options);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
lastError = error;
|
|
374
|
+
// Fatal failures (missing executable, deterministic early crash) never
|
|
375
|
+
// recover; only transient launch stalls get another attempt.
|
|
376
|
+
if (error && error.fatal) throw error;
|
|
377
|
+
if (attempt < attempts) console.log(`[cdp-browser] launch attempt ${attempt}/${attempts} failed (${String(error && error.message).split("\n")[0]}); retrying`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
throw lastError;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
module.exports = { isWindowsChromeLauncher, platformLaunchArgs, launchCdpBrowser, requestJson, waitFor, wait };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Records what the real UI actually sent and received over HTTP.
|
|
3
|
+
//
|
|
4
|
+
// The import id and the per-module wiring payload are decided by the running
|
|
5
|
+
// product, so reading the UI source cannot stand in for this evidence: the
|
|
6
|
+
// captured request body proves which import id travelled with the request,
|
|
7
|
+
// and the captured response body proves which statuses the product reported.
|
|
8
|
+
|
|
9
|
+
function parseJson(text) {
|
|
10
|
+
if (!text) return null;
|
|
11
|
+
try { return JSON.parse(text); } catch { return null; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function attachNetworkCapture(browser) {
|
|
15
|
+
const requests = [];
|
|
16
|
+
const byId = new Map();
|
|
17
|
+
const onMessage = raw => {
|
|
18
|
+
let message;
|
|
19
|
+
try { message = JSON.parse(String(raw)); } catch { return; }
|
|
20
|
+
const params = message.params || {};
|
|
21
|
+
if (message.method === 'Network.requestWillBeSent') {
|
|
22
|
+
const request = params.request || {};
|
|
23
|
+
const entry = {
|
|
24
|
+
id: params.requestId,
|
|
25
|
+
method: request.method,
|
|
26
|
+
url: request.url,
|
|
27
|
+
postData: request.postData || '',
|
|
28
|
+
body: parseJson(request.postData),
|
|
29
|
+
status: null,
|
|
30
|
+
};
|
|
31
|
+
requests.push(entry);
|
|
32
|
+
byId.set(params.requestId, entry);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (message.method === 'Network.responseReceived' && byId.has(params.requestId)) {
|
|
36
|
+
const response = params.response || {};
|
|
37
|
+
byId.get(params.requestId).status = response.status === undefined ? null : response.status;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
browser.socket.on('message', onMessage);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
requests,
|
|
44
|
+
async enable() { await browser.send('Network.enable'); },
|
|
45
|
+
find(predicate) { return requests.filter(predicate); },
|
|
46
|
+
lastApi(method, pathname) {
|
|
47
|
+
const matches = requests.filter(entry => entry.method === method && entry.url.includes(pathname));
|
|
48
|
+
return matches.length ? matches[matches.length - 1] : null;
|
|
49
|
+
},
|
|
50
|
+
async body(entry) {
|
|
51
|
+
if (!entry) return null;
|
|
52
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
53
|
+
try {
|
|
54
|
+
const result = await browser.send('Network.getResponseBody', { requestId: entry.id });
|
|
55
|
+
return parseJson(result.body);
|
|
56
|
+
} catch {
|
|
57
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
},
|
|
62
|
+
detach() { browser.socket.off('message', onMessage); },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { attachNetworkCapture };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function buildCandidates() {
|
|
5
|
+
return [
|
|
6
|
+
['env', process.env.KB_CHROME_PATH],
|
|
7
|
+
['chrome-localappdata', process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe')],
|
|
8
|
+
['chrome-programfiles', process.env.PROGRAMFILES && path.join(process.env.PROGRAMFILES, 'Google', 'Chrome', 'Application', 'chrome.exe')],
|
|
9
|
+
['chrome-programfiles-x86', process.env['PROGRAMFILES(X86)'] && path.join(process.env['PROGRAMFILES(X86)'], 'Google', 'Chrome', 'Application', 'chrome.exe')],
|
|
10
|
+
['edge-programfiles', process.env.PROGRAMFILES && path.join(process.env.PROGRAMFILES, 'Microsoft', 'Edge', 'Application', 'msedge.exe')],
|
|
11
|
+
['edge-programfiles-x86', process.env['PROGRAMFILES(X86)'] && path.join(process.env['PROGRAMFILES(X86)'], 'Microsoft', 'Edge', 'Application', 'msedge.exe')],
|
|
12
|
+
['linux-google-chrome', '/usr/bin/google-chrome'],
|
|
13
|
+
['linux-google-chrome-stable', '/usr/bin/google-chrome-stable'],
|
|
14
|
+
['linux-chromium', '/usr/bin/chromium'],
|
|
15
|
+
['linux-chromium-browser', '/usr/bin/chromium-browser'],
|
|
16
|
+
['macos-chrome', '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'],
|
|
17
|
+
].filter(entry => entry[1]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function findChromeDetailed() {
|
|
21
|
+
const candidates = buildCandidates();
|
|
22
|
+
for (const [source, candidate] of candidates) {
|
|
23
|
+
if (fs.existsSync(candidate)) return { path: candidate, source };
|
|
24
|
+
}
|
|
25
|
+
const error = new Error(`Chrome/Chromium not found; checked: ${candidates.map(entry => entry[1]).join(', ')}`);
|
|
26
|
+
error.code = 'KB_CHROME_NOT_FOUND';
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findChrome() {
|
|
31
|
+
return findChromeDetailed().path;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { findChrome, findChromeDetailed };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const net = require('node:net');
|
|
2
|
+
module.exports = async function freePort() {
|
|
3
|
+
const probe = net.createServer();
|
|
4
|
+
await new Promise((resolve, reject) => { probe.once('error', reject); probe.listen(0, '127.0.0.1', resolve); });
|
|
5
|
+
const port = probe.address().port;
|
|
6
|
+
await new Promise(resolve => probe.close(resolve));
|
|
7
|
+
return port;
|
|
8
|
+
};
|