@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,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// WB-002 — the Workbench module hosted for real, independently of the
|
|
3
|
+
// knowledge-base product.
|
|
4
|
+
//
|
|
5
|
+
// Two independent hosts are started from the REAL installed module (the copy
|
|
6
|
+
// the installed product 5.0.24 ships; the repository pin is byte-identical to
|
|
7
|
+
// it, proven by parallel-workbench-source-match.js):
|
|
8
|
+
//
|
|
9
|
+
// host A (standalone) no knowledge engine URL, no profile injection:
|
|
10
|
+
// the module must work with nothing but its own
|
|
11
|
+
// isolated data directory — the proof that it does
|
|
12
|
+
// not depend on knowledge-base business objects.
|
|
13
|
+
// host B (integration) KB_KNOWLEDGE_ENGINE_URL points at this lane's
|
|
14
|
+
// controlled integration fixture; the module's own
|
|
15
|
+
// KnowledgeContext contract is verified against
|
|
16
|
+
// that fixture, separately from the standalone
|
|
17
|
+
// host. The fixture is a FIXTURE: it stands in for
|
|
18
|
+
// a knowledge engine and is never counted as real
|
|
19
|
+
// knowledge or real retrieval.
|
|
20
|
+
//
|
|
21
|
+
// The model call itself is NOT faked here and is NOT performed here either:
|
|
22
|
+
// the real model turn lives in parallel-workbench-model-probe.js, which runs
|
|
23
|
+
// one real subscription call through an isolated integration host.
|
|
24
|
+
//
|
|
25
|
+
// Isolation: every directory, port, HOME and Claude config dir of both hosts
|
|
26
|
+
// is isolated; the live product ports are refused; nothing under the real
|
|
27
|
+
// product data directories is read for writing or written.
|
|
28
|
+
const assert = require('node:assert/strict');
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
const fixture = require('./helpers/parallel-workbench-fixture');
|
|
32
|
+
const sourceMap = require('./helpers/parallel-workbench-source-map');
|
|
33
|
+
|
|
34
|
+
const results = [];
|
|
35
|
+
function record(caseId, status, detail) {
|
|
36
|
+
results.push({ caseId, status, detail });
|
|
37
|
+
console.log(`CASE ${caseId} ${status} ${JSON.stringify(detail)}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function getJson(url, options) {
|
|
41
|
+
const response = await fetch(url, { ...options, signal: AbortSignal.timeout(30000) });
|
|
42
|
+
const body = await response.json().catch(() => null);
|
|
43
|
+
return { status: response.status, ok: response.ok, body };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function startHost(layout, moduleRoot, port, mode, extraEnv = {}) {
|
|
47
|
+
const child = fixture.spawnNode([path.join(__dirname, 'helpers', 'parallel-workbench-host.js')], fixture.hostEnv(layout, {
|
|
48
|
+
PK_WB_MODULE_ROOT: moduleRoot,
|
|
49
|
+
PK_WB_HOST_PORT: String(port),
|
|
50
|
+
PK_WB_HOST_DATA_DIR: layout.dataDir,
|
|
51
|
+
PK_WB_HOST_WORKSPACE: layout.workspace,
|
|
52
|
+
PK_WB_HOST_MODE: mode,
|
|
53
|
+
...extraEnv,
|
|
54
|
+
}));
|
|
55
|
+
return child;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function snapshotRealProfileDir() {
|
|
59
|
+
// Byte-level in-memory SHA256 snapshot; the digests never leave the process.
|
|
60
|
+
return fixture.digestDirectory(fixture.realWorkbenchProfileDir());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
(async () => {
|
|
64
|
+
const moduleRoot = fixture.resolveHostModuleRoot();
|
|
65
|
+
assert.ok(moduleRoot, 'no claude-ai-workbench module copy was found to host');
|
|
66
|
+
const identity = sourceMap.packageIdentity(moduleRoot);
|
|
67
|
+
const manifest = sourceMap.loadManifest();
|
|
68
|
+
const moduleRootLabel = moduleRoot === sourceMap.resolveInstalledProductPackage()
|
|
69
|
+
? 'currently-installed-package:installed-product'
|
|
70
|
+
: moduleRoot === sourceMap.resolveRepoTargetPackage()
|
|
71
|
+
? 'currently-installed-package:repository-pin'
|
|
72
|
+
: 'explicit-override';
|
|
73
|
+
console.log(`hosting module ${moduleRootLabel} ${identity.name}@${identity.version} from ${moduleRoot}`);
|
|
74
|
+
|
|
75
|
+
const realProfilesBefore = snapshotRealProfileDir();
|
|
76
|
+
let standalone;
|
|
77
|
+
let integration;
|
|
78
|
+
let integrationFixture;
|
|
79
|
+
|
|
80
|
+
// ---------- host A: standalone independent host -------------------------
|
|
81
|
+
const layoutA = fixture.createIsolationLayout('parallel-wb-indep');
|
|
82
|
+
const portA = await fixture.freePort();
|
|
83
|
+
const baseA = `http://127.0.0.1:${portA}`;
|
|
84
|
+
standalone = await startHost(layoutA, moduleRoot, portA, 'standalone');
|
|
85
|
+
try {
|
|
86
|
+
await fixture.waitForHttp(`${baseA}/api/claude-workbench/v1/health`, 30000);
|
|
87
|
+
|
|
88
|
+
const health = await getJson(`${baseA}/api/claude-workbench/v1/health`);
|
|
89
|
+
record('WB-002.a-standalone-health', health.ok && health.body && health.body.status === 'ok' ? 'PASS' : 'FAIL', {
|
|
90
|
+
httpStatus: health.status,
|
|
91
|
+
status: health.body && health.body.status,
|
|
92
|
+
version: health.body && health.body.version,
|
|
93
|
+
apiVersion: health.body && health.body.apiVersion,
|
|
94
|
+
expectVersion: identity.version,
|
|
95
|
+
});
|
|
96
|
+
assert.equal(health.body.status, 'ok');
|
|
97
|
+
assert.equal(health.body.version, identity.version);
|
|
98
|
+
|
|
99
|
+
const runtime = await getJson(`${baseA}/api/claude-workbench/v1/runtime`);
|
|
100
|
+
record('WB-002.b-standalone-runtime', runtime.ok && runtime.body && runtime.body.runtime === 'claude-code' ? 'PASS' : 'FAIL', {
|
|
101
|
+
body: runtime.body,
|
|
102
|
+
});
|
|
103
|
+
assert.equal(runtime.body.runtime, 'claude-code');
|
|
104
|
+
assert.equal(runtime.body.transport, 'agent-sdk');
|
|
105
|
+
|
|
106
|
+
const agents = await getJson(`${baseA}/api/claude-workbench/v1/agents`);
|
|
107
|
+
const agentIds = agents.body && Array.isArray(agents.body.agents) ? agents.body.agents.map(agent => agent.id) : [];
|
|
108
|
+
record('WB-002.c-standalone-agents', agents.ok && agentIds.includes('claude-code') ? 'PASS' : 'FAIL', { agentIds });
|
|
109
|
+
assert.ok(agentIds.includes('claude-code'), 'the claude-code agent is not listed');
|
|
110
|
+
|
|
111
|
+
// No knowledge engine, no profile injection: the module must still serve
|
|
112
|
+
// its own profile and project surfaces from the isolated data directory.
|
|
113
|
+
const profiles = await getJson(`${baseA}/api/claude-workbench/v1/profiles`);
|
|
114
|
+
const profileList = Array.isArray(profiles.body && profiles.body.profiles) ? profiles.body.profiles : [];
|
|
115
|
+
record('WB-002.d-standalone-profiles-isolated', profiles.ok && profileList.length === 0 ? 'PASS' : 'FAIL', {
|
|
116
|
+
profileCount: profileList.length,
|
|
117
|
+
detail: 'the isolated store starts empty; the real profile store is never consulted by the standalone host',
|
|
118
|
+
});
|
|
119
|
+
assert.equal(profileList.length, 0, 'the standalone host must not see real profiles');
|
|
120
|
+
|
|
121
|
+
const projects = await getJson(`${baseA}/api/claude-workbench/v1/projects`);
|
|
122
|
+
record('WB-002.e-standalone-projects', projects.ok ? 'PASS' : 'FAIL', { httpStatus: projects.status });
|
|
123
|
+
assert.ok(projects.ok);
|
|
124
|
+
|
|
125
|
+
const session = await getJson(`${baseA}/api/claude-workbench/v1/sessions`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: { 'Content-Type': 'application/json' },
|
|
128
|
+
body: JSON.stringify({ context: { contextId: 'wb002-standalone', workspaceRef: 'wb-fixture' } }),
|
|
129
|
+
});
|
|
130
|
+
const created = session.body && session.body.session;
|
|
131
|
+
record('WB-002.f-standalone-session-created', session.status === 201 && created && created.sessionId ? 'PASS' : 'FAIL', {
|
|
132
|
+
httpStatus: session.status,
|
|
133
|
+
sessionId: created && created.sessionId,
|
|
134
|
+
state: created && created.state,
|
|
135
|
+
agentId: created && created.agentId,
|
|
136
|
+
authSource: created && created.authSource,
|
|
137
|
+
});
|
|
138
|
+
assert.equal(session.status, 201);
|
|
139
|
+
assert.ok(created.sessionId);
|
|
140
|
+
|
|
141
|
+
// The session must exist with NO knowledge context: the module works
|
|
142
|
+
// without knowledge-base business objects.
|
|
143
|
+
const knowledge = await getJson(`${baseA}/api/claude-workbench/v1/sessions/${encodeURIComponent(created.sessionId)}/knowledge`);
|
|
144
|
+
record('WB-002.g-standalone-no-knowledge-context', knowledge.ok && knowledge.body && knowledge.body.status === 'not-retrieved' ? 'PASS' : 'FAIL', {
|
|
145
|
+
knowledgeStatus: knowledge.body && knowledge.body.status,
|
|
146
|
+
detail: 'no engine URL is configured, so nothing was retrieved — the session still started',
|
|
147
|
+
});
|
|
148
|
+
assert.equal(knowledge.body.status, 'not-retrieved');
|
|
149
|
+
|
|
150
|
+
// The session artefacts must live in the ISOLATED data directory.
|
|
151
|
+
await fixture.waitForHttp(`${baseA}/api/claude-workbench/v1/health`, 2000).catch(() => {});
|
|
152
|
+
const isolatedFiles = fs.existsSync(layoutA.dataDir) ? fs.readdirSync(layoutA.dataDir) : [];
|
|
153
|
+
record('WB-002.h-standalone-data-isolated', isolatedFiles.length > 0 ? 'PASS' : 'FAIL', {
|
|
154
|
+
dataDir: layoutA.dataDir,
|
|
155
|
+
entries: isolatedFiles.slice(0, 12),
|
|
156
|
+
});
|
|
157
|
+
assert.ok(isolatedFiles.length > 0, 'the standalone host wrote nothing into its isolated data directory');
|
|
158
|
+
} finally {
|
|
159
|
+
standalone.child.kill();
|
|
160
|
+
await standalone.exited;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------- host B: integration host + controlled fixture ---------------
|
|
164
|
+
const layoutB = fixture.createIsolationLayout('parallel-wb-integ');
|
|
165
|
+
integrationFixture = await fixture.startIntegrationFixture();
|
|
166
|
+
const portB = await fixture.freePort();
|
|
167
|
+
const baseB = `http://127.0.0.1:${portB}`;
|
|
168
|
+
integration = await startHost(layoutB, moduleRoot, portB, 'integration', {
|
|
169
|
+
PK_WB_ENGINE_URL: integrationFixture.url,
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
await fixture.waitForHttp(`${baseB}/api/claude-workbench/v1/health`, 30000);
|
|
173
|
+
|
|
174
|
+
// The integration contract, verified through the module's own
|
|
175
|
+
// KnowledgeContext against this lane's controlled fixture. The fixture is
|
|
176
|
+
// labelled: it is not real knowledge.
|
|
177
|
+
const { KnowledgeContext } = require(path.join(moduleRoot, 'packages', 'server', 'lib', 'knowledge-context.js'));
|
|
178
|
+
const context = new KnowledgeContext({ engineUrl: integrationFixture.url });
|
|
179
|
+
const retrieved = await context.retrieve(layoutB.workspace, 'integration contract probe', 'project', { sessionId: 'wb002-contract' });
|
|
180
|
+
const requestSeen = integrationFixture.requests[integrationFixture.requests.length - 1] || null;
|
|
181
|
+
const promptCarried = String(retrieved.prompt || '').includes(integrationFixture.marker);
|
|
182
|
+
record('WB-002.i-integration-context-contract', retrieved.status === 'ready' && promptCarried && requestSeen ? 'PASS' : 'FAIL', {
|
|
183
|
+
fixtureKind: 'controlled-integration-fixture (stand-in knowledge engine; never counted as real knowledge or real retrieval)',
|
|
184
|
+
engineUrl: integrationFixture.url,
|
|
185
|
+
retrievedStatus: retrieved.status,
|
|
186
|
+
references: retrieved.references ? retrieved.references.length : null,
|
|
187
|
+
memoryCount: retrieved.memoryCount == null ? null : retrieved.memoryCount,
|
|
188
|
+
promptCarriedMarker: promptCarried,
|
|
189
|
+
requestSeenByFixture: requestSeen && {
|
|
190
|
+
method: requestSeen.method,
|
|
191
|
+
url: requestSeen.url,
|
|
192
|
+
client: requestSeen.body && requestSeen.body.client,
|
|
193
|
+
repoPath: requestSeen.body && requestSeen.body.repoPath,
|
|
194
|
+
query: requestSeen.body && requestSeen.body.query,
|
|
195
|
+
sessionId: requestSeen.body && requestSeen.body.sessionId,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
assert.equal(retrieved.status, 'ready');
|
|
199
|
+
assert.ok(promptCarried, 'the retrieved prompt did not carry the fixture marker');
|
|
200
|
+
assert.equal(requestSeen.body.client, 'workbench', 'the module did not identify itself as the workbench client');
|
|
201
|
+
|
|
202
|
+
// The integration host itself must still create sessions; its knowledge
|
|
203
|
+
// surface stays empty until a real turn retrieves through the engine.
|
|
204
|
+
const session = await getJson(`${baseB}/api/claude-workbench/v1/sessions`, {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: { 'Content-Type': 'application/json' },
|
|
207
|
+
body: JSON.stringify({ context: { contextId: 'wb002-integration', workspaceRef: 'wb-fixture' } }),
|
|
208
|
+
});
|
|
209
|
+
const created = session.body && session.body.session;
|
|
210
|
+
record('WB-002.j-integration-session-created', session.status === 201 && created && created.sessionId ? 'PASS' : 'FAIL', {
|
|
211
|
+
httpStatus: session.status,
|
|
212
|
+
sessionId: created && created.sessionId,
|
|
213
|
+
});
|
|
214
|
+
assert.equal(session.status, 201);
|
|
215
|
+
} finally {
|
|
216
|
+
integration.child.kill();
|
|
217
|
+
await integration.exited;
|
|
218
|
+
await integrationFixture.close();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------- the real product surfaces stayed untouched -------------------
|
|
222
|
+
const realProfilesAfter = snapshotRealProfileDir();
|
|
223
|
+
const digestComparison = fixture.compareDigests(realProfilesBefore, realProfilesAfter);
|
|
224
|
+
record('WB-002.k-real-profiles-untouched', digestComparison.byteIdentical ? 'PASS' : 'FAIL', {
|
|
225
|
+
profileDir: fixture.realWorkbenchProfileDir(),
|
|
226
|
+
comparison: 'byte-level SHA256, computed in memory; digests are never printed, logged or persisted',
|
|
227
|
+
fileCount: digestComparison.count,
|
|
228
|
+
changedFiles: digestComparison.changedFiles,
|
|
229
|
+
addedFiles: digestComparison.addedFiles,
|
|
230
|
+
removedFiles: digestComparison.removedFiles,
|
|
231
|
+
});
|
|
232
|
+
assert.ok(digestComparison.byteIdentical, 'the real workbench profile directory changed during the run (byte-level comparison)');
|
|
233
|
+
|
|
234
|
+
const summary = {
|
|
235
|
+
lane: 'test-workbench',
|
|
236
|
+
file: 'parallel-workbench-independent-host.js',
|
|
237
|
+
case: 'WB-002',
|
|
238
|
+
title: '独立宿主',
|
|
239
|
+
module: { label: moduleRootLabel, root: moduleRoot, name: identity.name, version: identity.version, manifestVersion: manifest.terminal.version },
|
|
240
|
+
hosts: {
|
|
241
|
+
standalone: { port: portA, dataDir: layoutA.dataDir, workspace: layoutA.workspace, mode: 'standalone' },
|
|
242
|
+
integration: { port: portB, dataDir: layoutB.dataDir, workspace: layoutB.workspace, mode: 'integration', engineUrl: integrationFixture.url, fixtureKind: 'controlled-integration-fixture' },
|
|
243
|
+
},
|
|
244
|
+
modelCallPerformedHere: false,
|
|
245
|
+
modelProbe: 'parallel-workbench-model-probe.js',
|
|
246
|
+
networkUsed: false,
|
|
247
|
+
forbiddenPortsAvoided: [...fixture.FORBIDDEN_PORTS],
|
|
248
|
+
cases: results,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const target = process.env.PARALLEL_WORKBENCH_EVIDENCE;
|
|
252
|
+
if (target) {
|
|
253
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
254
|
+
fs.writeFileSync(target, JSON.stringify(summary, null, 2), 'utf8');
|
|
255
|
+
}
|
|
256
|
+
fixture.removeTree(layoutA.root);
|
|
257
|
+
fixture.removeTree(layoutB.root);
|
|
258
|
+
console.log('parallel-workbench-independent-host PASS');
|
|
259
|
+
})().catch(error => {
|
|
260
|
+
console.error(error);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// P-WB-QA-INTEGRATION-5025 canonical copy of the accepted WB-008 entry
|
|
3
|
+
// (_site/_test/parallel-workbench-runtime-wb008-abort.js, frozen untracked).
|
|
4
|
+
// P-WB-CANONICAL-R2 wiring: the entry goes through the canonical launcher boot
|
|
5
|
+
// (paid gate + explicit real-profile authorization → read-only discovery with
|
|
6
|
+
// the real environment → FULL driver isolation incl. APPDATA/LOCALAPPDATA/
|
|
7
|
+
// CODEX_HOME → carried-through captures), and the real-profile digest helpers
|
|
8
|
+
// come from the launcher's authorized layer instead of the pre-R5 tracked
|
|
9
|
+
// fixture whose whitelist lacked those keys. Case logic and expecteds are
|
|
10
|
+
// unchanged. Real paid model probe (two turns, real network to the provider) —
|
|
11
|
+
// run only explicitly via the integration-5025 runner with
|
|
12
|
+
// PK_WB_ALLOW_PAID_TURNS=1 and an explicit PK_WB_REAL_PROFILE_DIR.
|
|
13
|
+
// WB-008 — 中止隔离(真实浏览器 / 真实 0.13.6 隔离宿主 / 真实 MiniMax)。
|
|
14
|
+
//
|
|
15
|
+
// 本入口用真实 Chrome 打开模块自带的 agent-terminal UI(安装包内
|
|
16
|
+
// apps/agent-terminal),在隔离宿主上跑两个同时进行的真实会话 A / B:
|
|
17
|
+
//
|
|
18
|
+
// * A 与 B 都是真实模型请求,两个会话同时在跑;
|
|
19
|
+
// * 只通过产品自己的 UI 停止 B:切到 B,点发送按钮(忙碌时该按钮就是
|
|
20
|
+
// 产品自己的“停止”控件),不做任何绕过 UI 的中止调用;
|
|
21
|
+
// * 判定只看产品自身的事实:B 的流以 claude/aborted 结束且没有
|
|
22
|
+
// claaude/result、B 的 CLI 进程真的退出、A 的 CLI 进程还在、A 的流
|
|
23
|
+
// 在中止之后继续并且最后以 claude/result 正常完成。
|
|
24
|
+
//
|
|
25
|
+
// 进程普查只读,且只取本用例自己的宿主子进程子树;5757/5760 与任何
|
|
26
|
+
// 未知进程都不触碰。
|
|
27
|
+
//
|
|
28
|
+
// 退出码:0 = 全部证明;3 = 诚实 BLOCKED;1 = 脚本错误。
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const crypto = require('node:crypto');
|
|
31
|
+
const launcher = require('./helpers/parallel-workbench-integration-5025-launcher');
|
|
32
|
+
const fixture = require('./helpers/parallel-workbench-integration-5025-fixture');
|
|
33
|
+
const harness = require('./helpers/parallel-workbench-integration-5025-runtime-harness');
|
|
34
|
+
|
|
35
|
+
const PROFILE_ID = String(process.env.PK_WB_MODEL_PROFILE || 'minimax');
|
|
36
|
+
const REQUESTED_MODEL = String(process.env.PK_WB_MODEL_ID || 'MiniMax-M3.1-Flash');
|
|
37
|
+
const TURN_TIMEOUT_MS = Number(process.env.PK_WB_TURN_TIMEOUT_MS || 420000);
|
|
38
|
+
|
|
39
|
+
const results = [];
|
|
40
|
+
function record(caseId, status, detail) {
|
|
41
|
+
results.push({ caseId, status, detail });
|
|
42
|
+
console.log(`CASE ${caseId} ${status} ${JSON.stringify(detail)}`);
|
|
43
|
+
}
|
|
44
|
+
function assertTrue(condition, message) {
|
|
45
|
+
if (!condition) throw new Error(message);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The runtime processes of THIS host child only.
|
|
49
|
+
function runtimeProcesses(hostPid) {
|
|
50
|
+
return harness.runtimeProcesses(hostPid);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function waitForRuntimeProcesses(hostPid, minimum, timeoutMs) {
|
|
54
|
+
const deadline = Date.now() + timeoutMs;
|
|
55
|
+
let last = [];
|
|
56
|
+
while (Date.now() < deadline) {
|
|
57
|
+
last = runtimeProcesses(hostPid);
|
|
58
|
+
if (last.length >= minimum) return last;
|
|
59
|
+
await harness.wait(250);
|
|
60
|
+
}
|
|
61
|
+
return last;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
(async () => {
|
|
65
|
+
// The single safe entry discipline: gates, real-env discovery, full driver
|
|
66
|
+
// isolation, carried-through captures — before anything else happens.
|
|
67
|
+
const boot = launcher.boot({ prefix: 'parallel-wb008', paid: true, needsRealProfile: true });
|
|
68
|
+
const realProfilesBefore = launcher.digestDirectory(boot.discovery.realProfileDir);
|
|
69
|
+
const host = await harness.startHost({
|
|
70
|
+
prefix: 'parallel-wb008',
|
|
71
|
+
moduleRoot: boot.discovery.moduleRoot,
|
|
72
|
+
realProfileDir: boot.discovery.realProfileDir,
|
|
73
|
+
});
|
|
74
|
+
const identity = harness.moduleIdentity(host.moduleRoot);
|
|
75
|
+
const hostPid = host.child.child.pid;
|
|
76
|
+
let browser = null;
|
|
77
|
+
let outcome = null;
|
|
78
|
+
|
|
79
|
+
const tokenA = `WBRT-ABORT-A-${crypto.randomBytes(5).toString('hex')}`;
|
|
80
|
+
const tokenB = `WBRT-ABORT-B-${crypto.randomBytes(5).toString('hex')}`;
|
|
81
|
+
// A long answer keeps session A running while B is aborted.
|
|
82
|
+
const questionA = `这是 WB-008 中止隔离的真实抽测(第 1 问,会话 A)。请输出 400 行,每行形如 "行 0001 编号 xxxxx",最后一行原样包含标记 ${tokenA}。`;
|
|
83
|
+
const questionB = `这是 WB-008 中止隔离的真实抽测(第 2 问,会话 B)。请输出 200 行,每行形如 "行 0001 编号 xxxxx",最后一行原样包含标记 ${tokenB}。`;
|
|
84
|
+
|
|
85
|
+
const eventsA = [];
|
|
86
|
+
const eventsB = [];
|
|
87
|
+
let abortAt = null;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const opened = await harness.openBrowser(host.base, host.layout, { chrome: boot.discovery.chrome });
|
|
91
|
+
browser = opened.browser;
|
|
92
|
+
|
|
93
|
+
const health = await harness.getJson(`${host.base}${harness.API_BASE}/health`);
|
|
94
|
+
const project = await harness.ensureProject(browser, path.join(host.layout.root, 'wb008-project'), { name: 'wb008-project' });
|
|
95
|
+
record('WB-008.a-real-browser-and-host', health.ok && health.body && health.body.status === 'ok' ? 'PASS' : 'BLOCKED', {
|
|
96
|
+
browser: 'real Chrome via CDP (headless), isolated user-data-dir below the isolated layout',
|
|
97
|
+
chrome: opened.chrome,
|
|
98
|
+
module: identity,
|
|
99
|
+
host: { mode: 'standalone', dataDir: host.layout.dataDir, workspace: host.layout.workspace, port: host.port, hostPid },
|
|
100
|
+
project: { id: project.projectId, name: project.name, createdThroughTheProductForm: project.created === true },
|
|
101
|
+
health: health.body,
|
|
102
|
+
});
|
|
103
|
+
assertTrue(health.ok && health.body && health.body.status === 'ok', 'the isolated host is not healthy');
|
|
104
|
+
|
|
105
|
+
const profiles = await harness.getJson(`${host.base}${harness.API_BASE}/profiles`);
|
|
106
|
+
const listed = Array.isArray(profiles.body && profiles.body.profiles) ? profiles.body.profiles : [];
|
|
107
|
+
const profile = listed.find(item => item.id === PROFILE_ID) || null;
|
|
108
|
+
assertTrue(profile, `the ${PROFILE_ID} profile is not visible through the read-only facade`);
|
|
109
|
+
await harness.selectModel(browser, profile.name, REQUESTED_MODEL);
|
|
110
|
+
const afterModel = await harness.readState(browser);
|
|
111
|
+
record('WB-008.b-model-selected-through-the-real-ui', afterModel.product.currentProfileId === PROFILE_ID && afterModel.product.currentModel === REQUESTED_MODEL ? 'PASS' : 'BLOCKED', {
|
|
112
|
+
profileId: PROFILE_ID,
|
|
113
|
+
profileName: profile.name,
|
|
114
|
+
requestedModel: REQUESTED_MODEL,
|
|
115
|
+
uiProfileId: afterModel.product.currentProfileId,
|
|
116
|
+
uiModel: afterModel.product.currentModel,
|
|
117
|
+
credentialConfigured: profile.credentialConfigured === true,
|
|
118
|
+
credentialWrittenAnywhere: false,
|
|
119
|
+
});
|
|
120
|
+
assertTrue(afterModel.product.currentProfileId === PROFILE_ID, 'the profile was not selected through the UI');
|
|
121
|
+
|
|
122
|
+
// --- session A ------------------------------------------------------
|
|
123
|
+
await harness.clickNewSession(browser);
|
|
124
|
+
const firstSession = await harness.waitForState(browser, state => state.product && state.product.activeSessionId, 'session A', 30000);
|
|
125
|
+
const sessionA = firstSession.product.activeSessionId;
|
|
126
|
+
const streamA = harness.consumeSessionEvents(
|
|
127
|
+
harness.sessionEventsUrl(host.base, sessionA),
|
|
128
|
+
event => eventsA.push(event),
|
|
129
|
+
event => event.type === 'claude/result' || event.type === 'claude/error',
|
|
130
|
+
TURN_TIMEOUT_MS,
|
|
131
|
+
);
|
|
132
|
+
await harness.typeText(browser, questionA);
|
|
133
|
+
await harness.clickSend(browser);
|
|
134
|
+
const aBusy = await harness.waitForState(browser, state => state.busy === true, 'session A to start', 90000);
|
|
135
|
+
const procsA = await waitForRuntimeProcesses(hostPid, 1, 90000);
|
|
136
|
+
record('WB-008.c-session-a-is-running', aBusy.busy === true && procsA.length >= 1 ? 'PASS' : 'BLOCKED', {
|
|
137
|
+
sessionId: sessionA,
|
|
138
|
+
uiSendTitleWhileBusy: aBusy.sendTitle,
|
|
139
|
+
uiSessionState: aBusy.product.sessionState,
|
|
140
|
+
runtimeProcessesOfThisHost: procsA,
|
|
141
|
+
tokenA,
|
|
142
|
+
});
|
|
143
|
+
assertTrue(procsA.length >= 1, 'session A has no runtime process');
|
|
144
|
+
|
|
145
|
+
// --- session B, started while A is still running --------------------
|
|
146
|
+
await harness.clickNewSession(browser);
|
|
147
|
+
const secondSession = await harness.waitForState(browser, state => state.product && state.product.activeSessionId && state.product.activeSessionId !== sessionA, 'session B', 30000);
|
|
148
|
+
const sessionB = secondSession.product.activeSessionId;
|
|
149
|
+
const streamB = harness.consumeSessionEvents(
|
|
150
|
+
harness.sessionEventsUrl(host.base, sessionB),
|
|
151
|
+
event => eventsB.push(event),
|
|
152
|
+
event => event.type === 'claude/result' || event.type === 'claude/aborted' || event.type === 'claude/error',
|
|
153
|
+
TURN_TIMEOUT_MS,
|
|
154
|
+
);
|
|
155
|
+
await harness.typeText(browser, questionB);
|
|
156
|
+
await harness.clickSend(browser);
|
|
157
|
+
const bBusy = await harness.waitForState(browser, state => state.busy === true, 'session B to start', 90000);
|
|
158
|
+
const procsB = await waitForRuntimeProcesses(hostPid, procsA.length + 1, 90000);
|
|
159
|
+
// The processes that were already running for A when B started are A's;
|
|
160
|
+
// the ones that appeared with B are B's. A short-lived helper of A may
|
|
161
|
+
// have exited on its own in between, so the mapping is the intersection
|
|
162
|
+
// and the difference, not the raw first census.
|
|
163
|
+
const aPids = procsA.filter(entry => procsB.some(other => other.pid === entry.pid)).map(entry => entry.pid);
|
|
164
|
+
const bPids = procsB.filter(entry => !procsA.some(other => other.pid === entry.pid)).map(entry => entry.pid);
|
|
165
|
+
const aStillRunning = aPids.length >= 1 && bPids.length >= 1;
|
|
166
|
+
record('WB-008.d-two-sessions-run-at-the-same-time', bBusy.busy === true && aStillRunning ? 'PASS' : 'BLOCKED', {
|
|
167
|
+
sessionA: { id: sessionA, runtimeProcesses: procsA.map(entry => ({ pid: entry.pid, name: entry.name, commandLine: entry.commandLine })) },
|
|
168
|
+
sessionB: { id: sessionB, runtimeProcesses: procsB.map(entry => ({ pid: entry.pid, name: entry.name, commandLine: entry.commandLine })) },
|
|
169
|
+
sessionAProcessPids: aPids,
|
|
170
|
+
sessionBProcessPids: bPids,
|
|
171
|
+
runtimeProcessesAddedForSessionB: bPids,
|
|
172
|
+
sessionAProcessStillAlive: aStillRunning,
|
|
173
|
+
uiSendTitleWhileBusy: bBusy.sendTitle,
|
|
174
|
+
});
|
|
175
|
+
assertTrue(aStillRunning, 'session A stopped running while session B started');
|
|
176
|
+
|
|
177
|
+
// --- abort B through the product's own stop control -----------------
|
|
178
|
+
await harness.openSessionRow(browser, project.projectId, sessionB);
|
|
179
|
+
const bBusyInUi = await harness.waitForState(browser, state => state.busy === true && state.product.activeSessionId === sessionB, 'session B to be busy in the UI', 30000);
|
|
180
|
+
const beforeAbort = runtimeProcesses(hostPid);
|
|
181
|
+
abortAt = Date.now();
|
|
182
|
+
await harness.clickSend(browser);
|
|
183
|
+
const bIdle = await harness.waitForState(browser, state => state.busy === false && state.product.activeSessionId === sessionB, 'session B to become idle after the stop', 60000);
|
|
184
|
+
await harness.wait(3000);
|
|
185
|
+
const afterAbort = runtimeProcesses(hostPid);
|
|
186
|
+
const afterPids = new Set(afterAbort.map(entry => entry.pid));
|
|
187
|
+
const exitedPids = beforeAbort.map(entry => entry.pid).filter(pid => !afterPids.has(pid));
|
|
188
|
+
const survivedA = aPids.filter(pid => afterPids.has(pid));
|
|
189
|
+
const bStillAlive = bPids.filter(pid => afterPids.has(pid));
|
|
190
|
+
const bAborted = eventsB.some(event => event.type === 'claude/aborted');
|
|
191
|
+
const bResulted = eventsB.some(event => event.type === 'claude/result');
|
|
192
|
+
const stopNoteInUi = bIdle.streamText.includes('已停止');
|
|
193
|
+
const abortOk = bAborted && !bResulted && exitedPids.length >= 1
|
|
194
|
+
&& survivedA.length === aPids.length && survivedA.length >= 1
|
|
195
|
+
&& bStillAlive.length === 0
|
|
196
|
+
&& bIdle.busy === false;
|
|
197
|
+
record('WB-008.e-only-b-was-aborted', abortOk ? 'PASS' : 'BLOCKED', {
|
|
198
|
+
abortControl: 'the product\'s own send button, which the product itself turns into “停止” while the turn runs (btn-send.title === "停止")',
|
|
199
|
+
sendTitleWhileBusy: bBusyInUi.sendTitle,
|
|
200
|
+
sessionB: {
|
|
201
|
+
sessionId: sessionB,
|
|
202
|
+
abortedEvent: bAborted,
|
|
203
|
+
resultEvent: bResulted,
|
|
204
|
+
eventTypes: eventsB.map(event => event.type),
|
|
205
|
+
uiSendTitleAfterAbort: bIdle.sendTitle,
|
|
206
|
+
uiSessionStateAfterAbort: bIdle.product.sessionState,
|
|
207
|
+
stopNoteRenderedInTheTranscript: stopNoteInUi,
|
|
208
|
+
},
|
|
209
|
+
processCensus: {
|
|
210
|
+
scope: 'the descendants of this case\'s own host child only',
|
|
211
|
+
beforeAbort: beforeAbort.map(entry => ({ pid: entry.pid, name: entry.name, commandLine: entry.commandLine })),
|
|
212
|
+
afterAbort: afterAbort.map(entry => ({ pid: entry.pid, name: entry.name, commandLine: entry.commandLine })),
|
|
213
|
+
exitedPids,
|
|
214
|
+
sessionAPidsBeforeTheAbort: aPids,
|
|
215
|
+
sessionAPidsStillAlive: survivedA,
|
|
216
|
+
sessionBPidsBeforeTheAbort: bPids,
|
|
217
|
+
sessionBPidsStillAlive: bStillAlive,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
assertTrue(abortOk, 'the abort did not isolate B');
|
|
221
|
+
|
|
222
|
+
// --- A keeps running and finishes on its own ------------------------
|
|
223
|
+
// The UI is put back onto A while A is still running, so the answer of A
|
|
224
|
+
// is observed in the product's own live rendering. The completion gate is
|
|
225
|
+
// the session's own result event, not the marker inside the answer.
|
|
226
|
+
await harness.openSessionRow(browser, project.projectId, sessionA);
|
|
227
|
+
await harness.waitUntil(() => eventsA.some(event => event.type === 'claude/result'), 'the result event of session A', TURN_TIMEOUT_MS, 500);
|
|
228
|
+
const aAnswerInUi = await harness.waitForState(
|
|
229
|
+
browser,
|
|
230
|
+
state => state.product && state.product.activeSessionId === sessionA && state.busy === false
|
|
231
|
+
&& state.assistantMessages.some(text => text.includes(tokenA)),
|
|
232
|
+
'the finished answer of session A in the UI',
|
|
233
|
+
30000,
|
|
234
|
+
200,
|
|
235
|
+
);
|
|
236
|
+
const aEventsAfterAbort = eventsA.filter(event => Date.parse(event.at) >= abortAt - 500);
|
|
237
|
+
const aResult = eventsA.find(event => event.type === 'claude/result') || null;
|
|
238
|
+
const aAnswer = aResult ? String(aResult.result || '') : '';
|
|
239
|
+
const aFinishedAfterAbort = aResult ? Date.parse(aResult.at) >= abortAt : false;
|
|
240
|
+
const aOk = aEventsAfterAbort.length > 0 && Boolean(aResult) && aFinishedAfterAbort
|
|
241
|
+
&& aAnswer.includes(tokenA);
|
|
242
|
+
record('WB-008.f-session-a-kept-running-and-completed', aOk ? 'PASS' : 'BLOCKED', {
|
|
243
|
+
sessionA: {
|
|
244
|
+
sessionId: sessionA,
|
|
245
|
+
eventCount: eventsA.length,
|
|
246
|
+
eventsAfterTheAbortOfB: aEventsAfterAbort.length,
|
|
247
|
+
eventTypesAfterTheAbortOfB: [...new Set(aEventsAfterAbort.map(event => event.type))],
|
|
248
|
+
resultAt: aResult ? aResult.at : null,
|
|
249
|
+
finishedAfterTheAbortOfB: aFinishedAfterAbort,
|
|
250
|
+
tokenEchoedInTheAnswer: aAnswer.includes(tokenA),
|
|
251
|
+
answerPreview: aAnswer.slice(0, 120),
|
|
252
|
+
tokenEchoedInTheUi: aAnswerInUi.assistantMessages.some(text => text.includes(tokenA)),
|
|
253
|
+
uiSendTitleAfterTheAnswer: aAnswerInUi.sendTitle,
|
|
254
|
+
uiSessionStateAfterTheAnswer: aAnswerInUi.product.sessionState,
|
|
255
|
+
},
|
|
256
|
+
paidTurns: 2,
|
|
257
|
+
});
|
|
258
|
+
assertTrue(aOk, 'session A did not keep running after B was aborted');
|
|
259
|
+
|
|
260
|
+
// --- the UI shows both facts ----------------------------------------
|
|
261
|
+
// The reopened session renders its own history, so the transcript is
|
|
262
|
+
// waited for instead of being read in the middle of the replay.
|
|
263
|
+
await harness.openSessionRow(browser, project.projectId, sessionB);
|
|
264
|
+
const bView = await harness.waitForState(browser, state => state.streamText.includes('已停止'), 'the stopped note of session B in the reopened transcript', 30000, 200);
|
|
265
|
+
const bTranscriptStopped = bView.streamText.includes('已停止');
|
|
266
|
+
const bHasNoAnswer = !bView.assistantMessages.some(text => text.includes(tokenB));
|
|
267
|
+
await harness.openSessionRow(browser, project.projectId, sessionA);
|
|
268
|
+
const aView = await harness.waitForState(browser, state => state.assistantMessages.some(text => text.includes(tokenA)), 'the answer of session A in the reopened transcript', 30000, 200);
|
|
269
|
+
const aHasAnswer = aView.assistantMessages.some(text => text.includes(tokenA));
|
|
270
|
+
const uiOk = bTranscriptStopped && bHasNoAnswer && aHasAnswer;
|
|
271
|
+
record('WB-008.g-the-ui-shows-the-abort-of-b-and-the-answer-of-a', uiOk ? 'PASS' : 'BLOCKED', {
|
|
272
|
+
sessionB: { stoppedNoteInTheTranscript: bTranscriptStopped, answerWithTheTokenPresent: bView.assistantMessages.some(text => text.includes(tokenB)), assistantMessageCount: bView.assistantMessages.length },
|
|
273
|
+
sessionA: { answerWithTheTokenPresent: aHasAnswer, assistantMessageCount: aView.assistantMessages.length },
|
|
274
|
+
reopenedFrom: 'the product\'s own session history (the newest page of the session\'s own history file)',
|
|
275
|
+
});
|
|
276
|
+
assertTrue(uiOk, 'the UI does not show the abort of B and the answer of A');
|
|
277
|
+
|
|
278
|
+
outcome = {
|
|
279
|
+
sessionA: { id: sessionA, tokenA, answered: aAnswer.includes(tokenA), events: eventsA.length, eventsAfterTheAbortOfB: aEventsAfterAbort.length },
|
|
280
|
+
sessionB: { id: sessionB, tokenB, aborted: bAborted, result: bResulted, events: eventsB.length },
|
|
281
|
+
processCensus: { beforeAbort: beforeAbort.map(entry => entry.pid), afterAbort: afterAbort.map(entry => entry.pid), exitedPids },
|
|
282
|
+
paidTurns: 2,
|
|
283
|
+
};
|
|
284
|
+
} catch (error) {
|
|
285
|
+
record('WB-008.runtime', 'BLOCKED', { error: String(error && error.message ? error.message : error) });
|
|
286
|
+
} finally {
|
|
287
|
+
if (browser) { try { await browser.close(); } catch { /* the browser is disposable */ } }
|
|
288
|
+
if (host.child) { await harness.stopHost(host); }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const digestComparison = launcher.compareDigests(realProfilesBefore, launcher.digestDirectory(boot.discovery.realProfileDir));
|
|
292
|
+
record('WB-008.h-real-profiles-byte-identical', digestComparison.byteIdentical ? 'PASS' : 'FAIL', {
|
|
293
|
+
profileDir: boot.discovery.realProfileDir,
|
|
294
|
+
comparison: 'byte-level SHA256, computed in memory; digests never printed, logged or persisted',
|
|
295
|
+
fileCount: digestComparison.count,
|
|
296
|
+
changedFiles: digestComparison.changedFiles,
|
|
297
|
+
addedFiles: digestComparison.addedFiles,
|
|
298
|
+
removedFiles: digestComparison.removedFiles,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const essential = [
|
|
302
|
+
'WB-008.a-real-browser-and-host',
|
|
303
|
+
'WB-008.b-model-selected-through-the-real-ui',
|
|
304
|
+
'WB-008.c-session-a-is-running',
|
|
305
|
+
'WB-008.d-two-sessions-run-at-the-same-time',
|
|
306
|
+
'WB-008.e-only-b-was-aborted',
|
|
307
|
+
'WB-008.f-session-a-kept-running-and-completed',
|
|
308
|
+
'WB-008.g-the-ui-shows-the-abort-of-b-and-the-answer-of-a',
|
|
309
|
+
'WB-008.h-real-profiles-byte-identical',
|
|
310
|
+
];
|
|
311
|
+
const proven = essential.every(id => (results.find(entry => entry.caseId === id) || {}).status === 'PASS');
|
|
312
|
+
|
|
313
|
+
const summary = {
|
|
314
|
+
lane: 'test-workbench-runtime',
|
|
315
|
+
file: 'parallel-workbench-runtime-wb008-abort.js',
|
|
316
|
+
case: 'WB-008',
|
|
317
|
+
title: '中止隔离(真实浏览器 + 真实模型 + 真实进程退出证明)',
|
|
318
|
+
module: identity,
|
|
319
|
+
host: { mode: 'standalone', engineUrlConfigured: false, dataDir: host.layout.dataDir, workspace: host.layout.workspace, hostPid },
|
|
320
|
+
profile: { id: PROFILE_ID, source: 'original-subscription-store read-only, credential in memory only', credentialWrittenAnywhere: false },
|
|
321
|
+
abortControl: 'the product\'s own send button in its own busy state (停止) — no out-of-band abort call',
|
|
322
|
+
processCensusScope: 'the descendants of this case\'s own host child only; 5757/5760 and every unknown process are never enumerated, signalled or stopped',
|
|
323
|
+
turn: outcome,
|
|
324
|
+
cases: results,
|
|
325
|
+
verdict: proven ? 'PASS (only B was aborted and terminated, A kept running and completed)' : 'BLOCKED',
|
|
326
|
+
paidTurnsSent: outcome ? outcome.paidTurns : 0,
|
|
327
|
+
quotaEndpointQueried: false,
|
|
328
|
+
networkUsed: true,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const evidenceTarget = process.env.PARALLEL_WORKBENCH_RUNTIME_EVIDENCE;
|
|
332
|
+
harness.writeEvidence(evidenceTarget, summary);
|
|
333
|
+
if (!proven) console.log(`SCENE KEPT ${host.layout.root}`);
|
|
334
|
+
else fixture.removeTree(host.layout.root);
|
|
335
|
+
console.log(`parallel-workbench-runtime-wb008-abort verdict=${summary.verdict}`);
|
|
336
|
+
process.exitCode = proven ? 0 : 3;
|
|
337
|
+
})().catch(error => {
|
|
338
|
+
console.error(error);
|
|
339
|
+
process.exitCode = 1;
|
|
340
|
+
});
|