@sanqianx/project-knowledge 5.0.18 → 5.0.20-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/.agents/plugins/marketplace.json +3 -3
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +14 -0
  4. package/LICENSE +201 -201
  5. package/NOTICE +5 -5
  6. package/README.md +349 -349
  7. package/_modules/knowledge-engine/README.md +4 -0
  8. package/_modules/knowledge-engine/src/analysis-contract.js +4 -3
  9. package/_modules/knowledge-engine/src/analysis-failures.js +39 -0
  10. package/_modules/knowledge-engine/src/analysis-worker.js +6 -2
  11. package/_modules/knowledge-engine/src/coordinator.js +8 -0
  12. package/_modules/knowledge-engine/src/headless-analyzer.js +2 -1
  13. package/_modules/knowledge-engine/src/memories.js +10 -5
  14. package/_modules/knowledge-engine/src/memory-eligibility.js +74 -0
  15. package/_modules/knowledge-engine/src/retrieval-ranking.js +19 -1
  16. package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +1 -0
  17. package/_modules/knowledge-engine/src/runtime/commit-prompt.js +12 -9
  18. package/_modules/knowledge-engine/src/runtime/conversation-store.js +2 -0
  19. package/_modules/knowledge-engine/src/server.js +1 -1
  20. package/_site/_test/agent-capture-mcp-ui-test.js +67 -67
  21. package/_site/_test/ai-profile-test.js +75 -75
  22. package/_site/_test/ai-vendor-presets-test.js +91 -91
  23. package/_site/_test/automatic-context-test.js +70 -70
  24. package/_site/_test/bin-cli-test.js +230 -230
  25. package/_site/_test/bridge-adapter-test.js +95 -95
  26. package/_site/_test/bridge-consumer-service-test.js +310 -310
  27. package/_site/_test/candidate-bin-content-test.js +35 -0
  28. package/_site/_test/candidate-product-assets-test.js +13 -0
  29. package/_site/_test/candidate-release-contract-test.js +14 -14
  30. package/_site/_test/capture-hook-dedup-test.js +158 -158
  31. package/_site/_test/claude-executable-discovery-test.js +28 -28
  32. package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
  33. package/_site/_test/cli-instance-management-test.js +62 -62
  34. package/_site/_test/client-capture-restart-e2e-test.js +189 -189
  35. package/_site/_test/codex-conversation-projection-test.js +60 -60
  36. package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
  37. package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
  38. package/_site/_test/codex-notify-fanout-test.js +134 -134
  39. package/_site/_test/codex-notify-upgrade-test.js +253 -253
  40. package/_site/_test/commit-boundary-freeze-test.js +147 -147
  41. package/_site/_test/commit-conversation-binding-test.js +4 -4
  42. package/_site/_test/configuration-generation-test.js +22 -22
  43. package/_site/_test/context-pack-test.js +200 -200
  44. package/_site/_test/conversation-api-test.js +10 -10
  45. package/_site/_test/conversation-origin-test.js +15 -0
  46. package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
  47. package/_site/_test/embedding-config-test.js +32 -32
  48. package/_site/_test/fixtures/fake-browser-exit.js +10 -10
  49. package/_site/_test/fixtures/fake-browser-hang.js +10 -10
  50. package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
  51. package/_site/_test/fixtures/make-git-repos.js +92 -92
  52. package/_site/_test/fixtures/retrieval-gold-queries.json +32 -0
  53. package/_site/_test/fresh-hook-reimport-test.js +53 -53
  54. package/_site/_test/fresh-product-data-test.js +56 -56
  55. package/_site/_test/git-path-compatibility-test.js +80 -80
  56. package/_site/_test/git-validation-test.js +81 -81
  57. package/_site/_test/hook-worktree-test.js +42 -42
  58. package/_site/_test/index-builder-test.js +41 -41
  59. package/_site/_test/index-error-display-contract-test.js +10 -10
  60. package/_site/_test/integration-adapters-test.js +67 -67
  61. package/_site/_test/integration-capture-setup-test.js +118 -118
  62. package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
  63. package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
  64. package/_site/_test/integration-runtime-binding-test.js +473 -473
  65. package/_site/_test/integration-status-scan-test.js +29 -29
  66. package/_site/_test/kb-framework-test.js +57 -57
  67. package/_site/_test/kbpath-follow-test.js +63 -63
  68. package/_site/_test/knowledge-db-test.js +144 -144
  69. package/_site/_test/knowledge-engine-client-test.js +109 -109
  70. package/_site/_test/knowledge-maintenance-test.js +40 -40
  71. package/_site/_test/knowledge-panel-ui-contract-test.js +112 -0
  72. package/_site/_test/knowledge-scopes-test.js +37 -37
  73. package/_site/_test/knowledge-storage-location-test.js +28 -28
  74. package/_site/_test/launcher-readiness-test.js +80 -80
  75. package/_site/_test/legacy-claude-session-alias-test.js +106 -106
  76. package/_site/_test/legacy-routes-removed-test.js +47 -47
  77. package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
  78. package/_site/_test/live-gateway-test.js +48 -48
  79. package/_site/_test/local-marketplace-test.js +16 -16
  80. package/_site/_test/log-redaction-test.js +58 -58
  81. package/_site/_test/markdown-maintenance-test.js +11 -11
  82. package/_site/_test/model-context-windows-test.js +89 -89
  83. package/_site/_test/module-agent-start-proxy-test.js +65 -65
  84. package/_site/_test/module-artifact-integrity-test.js +27 -27
  85. package/_site/_test/module-boundary-test.js +30 -30
  86. package/_site/_test/module-bridge-eventbridge-test.js +202 -202
  87. package/_site/_test/module-dynamic-port-test.js +118 -118
  88. package/_site/_test/module-index-proxy-test.js +33 -0
  89. package/_site/_test/module-model-configuration-test.js +44 -44
  90. package/_site/_test/module-process-lifecycle-test.js +35 -35
  91. package/_site/_test/module-stream-proxy-test.js +34 -34
  92. package/_site/_test/module-terminal-restart-test.js +63 -0
  93. package/_site/_test/module-wiring-unbind-race-test.js +59 -59
  94. package/_site/_test/native-context-integrations-test.js +86 -86
  95. package/_site/_test/orphaned-claim-recovery-test.js +80 -80
  96. package/_site/_test/package-boundary-test.js +43 -42
  97. package/_site/_test/package-startup-test.js +56 -56
  98. package/_site/_test/packaged-ui-smoke-test.js +88 -88
  99. package/_site/_test/pr-consumer-contract-test.js +55 -55
  100. package/_site/_test/product-brand-contract-test.js +25 -25
  101. package/_site/_test/product-diagnostics-ui-test.js +93 -93
  102. package/_site/_test/product-import-ui-test.js +104 -104
  103. package/_site/_test/product-progress-status-test.js +12 -1
  104. package/_site/_test/project-remove-running-guard-test.js +79 -79
  105. package/_site/_test/project-store-test.js +90 -90
  106. package/_site/_test/registry-events-test.js +38 -0
  107. package/_site/_test/release-version-sync-test.js +115 -115
  108. package/_site/_test/requirement-binding-test.js +113 -113
  109. package/_site/_test/runtime-endpoint-test.js +64 -64
  110. package/_site/_test/scanner-test.js +83 -83
  111. package/_site/_test/server-security-test.js +96 -96
  112. package/_site/_test/sessions-stream-test.js +93 -93
  113. package/_site/_test/shared-scope-root-binding-test.js +67 -67
  114. package/_site/_test/simple-import-test.js +98 -98
  115. package/_site/_test/startup-analysis-disabled-test.js +6 -6
  116. package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
  117. package/_site/_test/team-knowledge-store-test.js +132 -132
  118. package/_site/_test/test-server-cleanup-test.js +35 -35
  119. package/_site/_test/test-server-module-isolation-test.js +37 -37
  120. package/_site/_test/tracking-start-test.js +35 -35
  121. package/_site/_test/workbench-context-receipts-test.js +22 -0
  122. package/_site/_test/workspace-ui-contract-test.js +31 -31
  123. package/_site/_test/zcode-capture-integration-test.js +108 -108
  124. package/_site/lib/ai-vendor-presets.js +83 -83
  125. package/_site/lib/ai-workspace.js +3 -3
  126. package/_site/lib/atomic-file.js +204 -204
  127. package/_site/lib/bridge-adapter.js +260 -260
  128. package/_site/lib/capture-hook-config.js +108 -108
  129. package/_site/lib/claude-cli-runner.js +89 -89
  130. package/_site/lib/cli-instances.js +43 -43
  131. package/_site/lib/codex-context-status.js +17 -17
  132. package/_site/lib/codex-notify-config.js +193 -193
  133. package/_site/lib/commit-conversation-binder.js +13 -12
  134. package/_site/lib/commit-reconciler.js +38 -38
  135. package/_site/lib/context-pack-builder.js +299 -299
  136. package/_site/lib/conversation-exclusions.js +204 -204
  137. package/_site/lib/conversation-query-service.js +17 -17
  138. package/_site/lib/conversation-store.js +2 -0
  139. package/_site/lib/data-dir.js +13 -13
  140. package/_site/lib/embedding-service.js +85 -85
  141. package/_site/lib/engine-connection.js +93 -90
  142. package/_site/lib/engine-knowledge-client.js +72 -72
  143. package/_site/lib/folder-picker-output.js +3 -3
  144. package/_site/lib/git-path.js +14 -14
  145. package/_site/lib/github-team-store.js +3 -3
  146. package/_site/lib/index-builder.js +157 -157
  147. package/_site/lib/integration-status-scan.js +99 -99
  148. package/_site/lib/integration-status-worker.js +14 -14
  149. package/_site/lib/kb-framework.js +15 -15
  150. package/_site/lib/knowledge-db.js +363 -363
  151. package/_site/lib/knowledge-schema.js +121 -121
  152. package/_site/lib/live-gateway.js +78 -76
  153. package/_site/lib/llm-client.js +12 -12
  154. package/_site/lib/model-context-windows.js +82 -82
  155. package/_site/lib/module-bridge.js +874 -865
  156. package/_site/lib/module-port-resolver.js +57 -57
  157. package/_site/lib/post-commit-automation.js +54 -54
  158. package/_site/lib/product-data.js +35 -35
  159. package/_site/lib/project-lifecycle-service.js +14 -14
  160. package/_site/lib/project-registry-store.js +129 -129
  161. package/_site/lib/prompt-registry.js +148 -148
  162. package/_site/lib/registry-events.js +16 -0
  163. package/_site/lib/requirement-adapters.js +10 -10
  164. package/_site/lib/requirement-binder.js +112 -112
  165. package/_site/lib/runtime-control.js +89 -89
  166. package/_site/lib/runtime-endpoint.js +5 -5
  167. package/_site/lib/server-app.js +10 -1
  168. package/_site/lib/zcode-context-integration.js +60 -60
  169. package/_site/scripts/codex-notify-fanout.cjs +95 -95
  170. package/_site/scripts/install-module-candidates.js +69 -69
  171. package/_site/scripts/pack-module-candidates.js +89 -72
  172. package/_site/scripts/sync-product-content.js +28 -28
  173. package/_site/scripts/sync-release-version.js +164 -164
  174. package/_site/scripts/vendor-modules.js +49 -49
  175. package/_site/scripts/verify-module-runtime.js +50 -50
  176. package/_site/scripts/verify-product-runtime.js +36 -36
  177. package/_site/scripts/verify-retrieval-gold.js +83 -0
  178. package/_site/server.js +15 -15
  179. package/_site/start.bat +26 -26
  180. package/_site/stop.bat +11 -11
  181. package/bin/project-knowledge-integrations.js +254 -254
  182. package/bin/project-knowledge-kb.js +67 -67
  183. package/bin/project-knowledge-mcp.js +8 -8
  184. package/bin/project-knowledge.js +170 -170
  185. package/claude-prompts.json +21 -21
  186. package/docs/README.zh-CN.md +205 -205
  187. package/docs/assets/architecture.svg +181 -181
  188. package/docs/assets/logo.svg +49 -49
  189. package/docs/fresh-v5-start.md +42 -42
  190. package/docs/leader-glm-workbench-playbook.md +122 -119
  191. package/docs/project-registry-schema.md +11 -11
  192. package/docs/release-5.0.16-acceptance.md +176 -176
  193. package/docs/release-5.0.17-acceptance.md +117 -117
  194. package/docs/release-5.0.19-acceptance.md +39 -0
  195. package/docs/team-knowledge-mode-a-plan.md +73 -73
  196. package/module-runtime-manifest.json +126 -104
  197. package/package.json +110 -108
  198. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  199. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  200. package/plugins/project-knowledge/.mcp.json +1 -1
  201. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  202. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  203. package/templates/change.md +30 -30
  204. package/templates/project-readme.md +18 -18
  205. package/ui/index.html +1 -1
  206. package/ui/knowledge-panel.html +118 -0
  207. package/ui/product.css +8 -0
  208. package/ui/product.js +175 -8
  209. package/vendor-manifest.json +55 -53
@@ -1,134 +1,134 @@
1
- // Run: node _site/_test/codex-notify-fanout-test.js
2
- const assert = require('assert');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const path = require('path');
6
- const { spawn, spawnSync } = require('child_process');
7
-
8
- const ROOT = path.resolve(__dirname, '..', '..');
9
- const TEMP = fs.mkdtempSync(path.join(os.tmpdir(), `pk-codex-fanout-${process.pid}-`));
10
- const fanout = path.join(ROOT, '_site', 'scripts', 'codex-notify-fanout.cjs');
11
- const receiver = path.join(TEMP, 'receiver.cjs');
12
-
13
- fs.writeFileSync(receiver, [
14
- "let data = '';",
15
- "process.stdin.on('data', chunk => data += chunk);",
16
- "process.stdin.on('end', () => require('fs').writeFileSync(process.argv[2], JSON.stringify({",
17
- " stdin: data, args: process.argv.slice(3), sessionsRoot: process.env.CODEX_SESSIONS_ROOT || null,",
18
- '})));',
19
- ].join('\n'), 'utf8');
20
-
21
- function encoded(command) {
22
- return Buffer.from(JSON.stringify(command)).toString('base64');
23
- }
24
-
25
- function receiverCommand(output) {
26
- return [process.execPath, receiver, path.join(TEMP, output)];
27
- }
28
-
29
- function options(next, bridge) {
30
- return ['--next-base64', encoded(next), '--bridge-base64', encoded(bridge)];
31
- }
32
-
33
- async function received(fileName) {
34
- const file = path.join(TEMP, fileName);
35
- for (let attempt = 0; attempt < 150; attempt += 1) {
36
- if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf8'));
37
- await new Promise(resolve => setTimeout(resolve, 20));
38
- }
39
- throw new Error(`timed out waiting for ${fileName}`);
40
- }
41
-
42
- function run(args, input = '') {
43
- const result = spawnSync(process.execPath, [fanout, ...args], {
44
- input,
45
- encoding: 'utf8',
46
- windowsHide: true,
47
- timeout: 5000,
48
- });
49
- assert.strictEqual(result.status, 0, result.stderr || result.error?.message || 'fan-out process should succeed');
50
- assert.strictEqual(result.stderr, '', 'a broken notifier must not emit an unhandled child-process error');
51
- }
52
-
53
- function runWithOpenStdin(args) {
54
- return new Promise((resolve, reject) => {
55
- const child = spawn(process.execPath, [fanout, ...args], {
56
- stdio: ['pipe', 'ignore', 'pipe'],
57
- windowsHide: true,
58
- });
59
- let stderr = '';
60
- child.stderr.setEncoding('utf8');
61
- child.stderr.on('data', chunk => { stderr += chunk; });
62
- const timer = setTimeout(() => {
63
- child.kill();
64
- reject(new Error('argv notification waited for stdin to end'));
65
- }, 5000);
66
- child.once('error', error => { clearTimeout(timer); reject(error); });
67
- child.once('exit', status => {
68
- clearTimeout(timer);
69
- child.stdin.destroy();
70
- try {
71
- assert.strictEqual(status, 0, stderr || 'argv notification must succeed with stdin still open');
72
- assert.strictEqual(stderr, '');
73
- resolve();
74
- } catch (error) {
75
- reject(error);
76
- }
77
- });
78
- });
79
- }
80
-
81
- (async () => {
82
- const legacy = JSON.stringify({ session_id: 'codex-session-test', type: 'turn-ended' });
83
- run(options(receiverCommand('stdin-next.json'), receiverCommand('stdin-bridge.json')), legacy);
84
- const [stdinNext, stdinBridge] = await Promise.all([received('stdin-next.json'), received('stdin-bridge.json')]);
85
- assert.strictEqual(stdinNext.stdin, legacy, 'stdin notifier keeps the original payload bytes');
86
- assert.deepStrictEqual(stdinNext.args, [], 'stdin notification is not unexpectedly converted into argv');
87
- assert.strictEqual(stdinBridge.stdin, legacy, 'existing session_id payload remains unchanged');
88
- assert.deepStrictEqual(stdinBridge.args, [], 'Bridge always receives stdin');
89
-
90
- // This is the documented Codex notify shape, not an invented session event.
91
- const official = '{ "type": "agent-turn-complete", "thread-id": "codex-thread-test", "turn-id": "turn-41", "last-assistant-message": "摘要不应成为伪造的会话证据" }';
92
- await runWithOpenStdin([
93
- ...options(receiverCommand('argv-next.json'), receiverCommand('argv-bridge.json')),
94
- official,
95
- ]);
96
- const [argvNext, argvBridge] = await Promise.all([received('argv-next.json'), received('argv-bridge.json')]);
97
- assert.deepStrictEqual(argvNext.args, [official], 'third-party notifier receives the original single JSON argument');
98
- assert.strictEqual(argvNext.stdin, '', 'argv notifier must not also receive a duplicate stdin payload');
99
- const normalized = JSON.parse(argvBridge.stdin);
100
- assert.strictEqual(normalized.session_id, 'codex-thread-test', 'official thread-id wakes the matching Bridge rollout reader');
101
- assert.strictEqual(normalized.turn_id, 'turn-41');
102
- assert.strictEqual(normalized['last-assistant-message'], JSON.parse(official)['last-assistant-message']);
103
- assert.deepStrictEqual(argvBridge.args, [], 'official payload is adapted into Bridge stdin only');
104
- assert.strictEqual(argvBridge.sessionsRoot, process.env.CODEX_SESSIONS_ROOT || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'sessions'));
105
-
106
- run(options(receiverCommand('official-stdin-next.json'), receiverCommand('official-stdin-bridge.json')), official);
107
- const [officialStdinNext, officialStdinBridge] = await Promise.all([
108
- received('official-stdin-next.json'), received('official-stdin-bridge.json'),
109
- ]);
110
- assert.strictEqual(officialStdinNext.stdin, official, 'legacy stdin transport preserves official payload bytes for third parties');
111
- assert.strictEqual(JSON.parse(officialStdinBridge.stdin).session_id, 'codex-thread-test');
112
-
113
- const alias = JSON.stringify({ sessionId: 'already-authoritative', turnId: 'already-turn', 'thread-id': 'different-thread', 'turn-id': 'different-turn' });
114
- run(options(null, receiverCommand('alias-bridge.json')), alias);
115
- assert.strictEqual((await received('alias-bridge.json')).stdin, alias, 'existing authoritative legacy aliases must not be overridden');
116
-
117
- const missingExecutable = [path.join(TEMP, 'missing-notifier.exe')];
118
- run(options(missingExecutable, receiverCommand('after-next-failure.json')), legacy);
119
- assert.strictEqual((await received('after-next-failure.json')).stdin, legacy, 'failed third-party spawn cannot block Bridge');
120
- run([...options(receiverCommand('after-bridge-failure.json'), missingExecutable), official]);
121
- assert.deepStrictEqual((await received('after-bridge-failure.json')).args, [official], 'failed Bridge spawn cannot block third-party notifier');
122
- run(['--next-base64', 'not-json', '--bridge-base64', encoded(receiverCommand('after-invalid-next.json'))], legacy);
123
- assert.strictEqual((await received('after-invalid-next.json')).stdin, legacy, 'invalid encoded branch cannot block the valid branch');
124
-
125
- // A child closing stdin early must not cause an EPIPE crash or lose the other branch.
126
- run(options([process.execPath, '-e', 'process.exit(0)'], receiverCommand('after-closed-stdin.json')), JSON.stringify({ session_id: 'large-payload', padding: 'x'.repeat(1024 * 1024) }));
127
- assert.strictEqual(JSON.parse((await received('after-closed-stdin.json')).stdin).session_id, 'large-payload');
128
- console.log('codex-notify-fanout-test PASS');
129
- })().catch(error => {
130
- console.error(error.stack || error.message);
131
- process.exitCode = 1;
132
- }).finally(() => {
133
- fs.rmSync(TEMP, { recursive: true, force: true });
134
- });
1
+ // Run: node _site/_test/codex-notify-fanout-test.js
2
+ const assert = require('assert');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { spawn, spawnSync } = require('child_process');
7
+
8
+ const ROOT = path.resolve(__dirname, '..', '..');
9
+ const TEMP = fs.mkdtempSync(path.join(os.tmpdir(), `pk-codex-fanout-${process.pid}-`));
10
+ const fanout = path.join(ROOT, '_site', 'scripts', 'codex-notify-fanout.cjs');
11
+ const receiver = path.join(TEMP, 'receiver.cjs');
12
+
13
+ fs.writeFileSync(receiver, [
14
+ "let data = '';",
15
+ "process.stdin.on('data', chunk => data += chunk);",
16
+ "process.stdin.on('end', () => require('fs').writeFileSync(process.argv[2], JSON.stringify({",
17
+ " stdin: data, args: process.argv.slice(3), sessionsRoot: process.env.CODEX_SESSIONS_ROOT || null,",
18
+ '})));',
19
+ ].join('\n'), 'utf8');
20
+
21
+ function encoded(command) {
22
+ return Buffer.from(JSON.stringify(command)).toString('base64');
23
+ }
24
+
25
+ function receiverCommand(output) {
26
+ return [process.execPath, receiver, path.join(TEMP, output)];
27
+ }
28
+
29
+ function options(next, bridge) {
30
+ return ['--next-base64', encoded(next), '--bridge-base64', encoded(bridge)];
31
+ }
32
+
33
+ async function received(fileName) {
34
+ const file = path.join(TEMP, fileName);
35
+ for (let attempt = 0; attempt < 150; attempt += 1) {
36
+ if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf8'));
37
+ await new Promise(resolve => setTimeout(resolve, 20));
38
+ }
39
+ throw new Error(`timed out waiting for ${fileName}`);
40
+ }
41
+
42
+ function run(args, input = '') {
43
+ const result = spawnSync(process.execPath, [fanout, ...args], {
44
+ input,
45
+ encoding: 'utf8',
46
+ windowsHide: true,
47
+ timeout: 5000,
48
+ });
49
+ assert.strictEqual(result.status, 0, result.stderr || result.error?.message || 'fan-out process should succeed');
50
+ assert.strictEqual(result.stderr, '', 'a broken notifier must not emit an unhandled child-process error');
51
+ }
52
+
53
+ function runWithOpenStdin(args) {
54
+ return new Promise((resolve, reject) => {
55
+ const child = spawn(process.execPath, [fanout, ...args], {
56
+ stdio: ['pipe', 'ignore', 'pipe'],
57
+ windowsHide: true,
58
+ });
59
+ let stderr = '';
60
+ child.stderr.setEncoding('utf8');
61
+ child.stderr.on('data', chunk => { stderr += chunk; });
62
+ const timer = setTimeout(() => {
63
+ child.kill();
64
+ reject(new Error('argv notification waited for stdin to end'));
65
+ }, 5000);
66
+ child.once('error', error => { clearTimeout(timer); reject(error); });
67
+ child.once('exit', status => {
68
+ clearTimeout(timer);
69
+ child.stdin.destroy();
70
+ try {
71
+ assert.strictEqual(status, 0, stderr || 'argv notification must succeed with stdin still open');
72
+ assert.strictEqual(stderr, '');
73
+ resolve();
74
+ } catch (error) {
75
+ reject(error);
76
+ }
77
+ });
78
+ });
79
+ }
80
+
81
+ (async () => {
82
+ const legacy = JSON.stringify({ session_id: 'codex-session-test', type: 'turn-ended' });
83
+ run(options(receiverCommand('stdin-next.json'), receiverCommand('stdin-bridge.json')), legacy);
84
+ const [stdinNext, stdinBridge] = await Promise.all([received('stdin-next.json'), received('stdin-bridge.json')]);
85
+ assert.strictEqual(stdinNext.stdin, legacy, 'stdin notifier keeps the original payload bytes');
86
+ assert.deepStrictEqual(stdinNext.args, [], 'stdin notification is not unexpectedly converted into argv');
87
+ assert.strictEqual(stdinBridge.stdin, legacy, 'existing session_id payload remains unchanged');
88
+ assert.deepStrictEqual(stdinBridge.args, [], 'Bridge always receives stdin');
89
+
90
+ // This is the documented Codex notify shape, not an invented session event.
91
+ const official = '{ "type": "agent-turn-complete", "thread-id": "codex-thread-test", "turn-id": "turn-41", "last-assistant-message": "摘要不应成为伪造的会话证据" }';
92
+ await runWithOpenStdin([
93
+ ...options(receiverCommand('argv-next.json'), receiverCommand('argv-bridge.json')),
94
+ official,
95
+ ]);
96
+ const [argvNext, argvBridge] = await Promise.all([received('argv-next.json'), received('argv-bridge.json')]);
97
+ assert.deepStrictEqual(argvNext.args, [official], 'third-party notifier receives the original single JSON argument');
98
+ assert.strictEqual(argvNext.stdin, '', 'argv notifier must not also receive a duplicate stdin payload');
99
+ const normalized = JSON.parse(argvBridge.stdin);
100
+ assert.strictEqual(normalized.session_id, 'codex-thread-test', 'official thread-id wakes the matching Bridge rollout reader');
101
+ assert.strictEqual(normalized.turn_id, 'turn-41');
102
+ assert.strictEqual(normalized['last-assistant-message'], JSON.parse(official)['last-assistant-message']);
103
+ assert.deepStrictEqual(argvBridge.args, [], 'official payload is adapted into Bridge stdin only');
104
+ assert.strictEqual(argvBridge.sessionsRoot, process.env.CODEX_SESSIONS_ROOT || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'sessions'));
105
+
106
+ run(options(receiverCommand('official-stdin-next.json'), receiverCommand('official-stdin-bridge.json')), official);
107
+ const [officialStdinNext, officialStdinBridge] = await Promise.all([
108
+ received('official-stdin-next.json'), received('official-stdin-bridge.json'),
109
+ ]);
110
+ assert.strictEqual(officialStdinNext.stdin, official, 'legacy stdin transport preserves official payload bytes for third parties');
111
+ assert.strictEqual(JSON.parse(officialStdinBridge.stdin).session_id, 'codex-thread-test');
112
+
113
+ const alias = JSON.stringify({ sessionId: 'already-authoritative', turnId: 'already-turn', 'thread-id': 'different-thread', 'turn-id': 'different-turn' });
114
+ run(options(null, receiverCommand('alias-bridge.json')), alias);
115
+ assert.strictEqual((await received('alias-bridge.json')).stdin, alias, 'existing authoritative legacy aliases must not be overridden');
116
+
117
+ const missingExecutable = [path.join(TEMP, 'missing-notifier.exe')];
118
+ run(options(missingExecutable, receiverCommand('after-next-failure.json')), legacy);
119
+ assert.strictEqual((await received('after-next-failure.json')).stdin, legacy, 'failed third-party spawn cannot block Bridge');
120
+ run([...options(receiverCommand('after-bridge-failure.json'), missingExecutable), official]);
121
+ assert.deepStrictEqual((await received('after-bridge-failure.json')).args, [official], 'failed Bridge spawn cannot block third-party notifier');
122
+ run(['--next-base64', 'not-json', '--bridge-base64', encoded(receiverCommand('after-invalid-next.json'))], legacy);
123
+ assert.strictEqual((await received('after-invalid-next.json')).stdin, legacy, 'invalid encoded branch cannot block the valid branch');
124
+
125
+ // A child closing stdin early must not cause an EPIPE crash or lose the other branch.
126
+ run(options([process.execPath, '-e', 'process.exit(0)'], receiverCommand('after-closed-stdin.json')), JSON.stringify({ session_id: 'large-payload', padding: 'x'.repeat(1024 * 1024) }));
127
+ assert.strictEqual(JSON.parse((await received('after-closed-stdin.json')).stdin).session_id, 'large-payload');
128
+ console.log('codex-notify-fanout-test PASS');
129
+ })().catch(error => {
130
+ console.error(error.stack || error.message);
131
+ process.exitCode = 1;
132
+ }).finally(() => {
133
+ fs.rmSync(TEMP, { recursive: true, force: true });
134
+ });