@sanqianx/project-knowledge 5.0.19 → 5.0.20

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 (200) hide show
  1. package/.agents/plugins/marketplace.json +3 -3
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +5 -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-failures.js +39 -0
  9. package/_modules/knowledge-engine/src/analysis-worker.js +6 -2
  10. package/_modules/knowledge-engine/src/coordinator.js +7 -0
  11. package/_modules/knowledge-engine/src/headless-analyzer.js +2 -1
  12. package/_modules/knowledge-engine/src/server.js +1 -1
  13. package/_site/_test/agent-capture-mcp-ui-test.js +67 -67
  14. package/_site/_test/ai-profile-test.js +75 -75
  15. package/_site/_test/ai-vendor-presets-test.js +91 -91
  16. package/_site/_test/automatic-context-test.js +70 -70
  17. package/_site/_test/bin-cli-test.js +230 -230
  18. package/_site/_test/bridge-adapter-test.js +95 -95
  19. package/_site/_test/bridge-consumer-service-test.js +310 -310
  20. package/_site/_test/candidate-bin-content-test.js +35 -35
  21. package/_site/_test/candidate-product-assets-test.js +13 -13
  22. package/_site/_test/candidate-release-contract-test.js +14 -14
  23. package/_site/_test/capture-hook-dedup-test.js +158 -158
  24. package/_site/_test/claude-executable-discovery-test.js +28 -28
  25. package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
  26. package/_site/_test/cli-instance-management-test.js +62 -62
  27. package/_site/_test/client-capture-restart-e2e-test.js +189 -189
  28. package/_site/_test/codex-conversation-projection-test.js +60 -60
  29. package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
  30. package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
  31. package/_site/_test/codex-notify-fanout-test.js +134 -134
  32. package/_site/_test/codex-notify-upgrade-test.js +253 -253
  33. package/_site/_test/commit-boundary-freeze-test.js +147 -147
  34. package/_site/_test/commit-conversation-binding-test.js +4 -4
  35. package/_site/_test/configuration-generation-test.js +22 -22
  36. package/_site/_test/context-pack-test.js +200 -200
  37. package/_site/_test/conversation-api-test.js +10 -10
  38. package/_site/_test/conversation-origin-test.js +15 -15
  39. package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
  40. package/_site/_test/embedding-config-test.js +32 -32
  41. package/_site/_test/fixtures/fake-browser-exit.js +10 -10
  42. package/_site/_test/fixtures/fake-browser-hang.js +10 -10
  43. package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
  44. package/_site/_test/fixtures/make-git-repos.js +92 -92
  45. package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
  46. package/_site/_test/fresh-hook-reimport-test.js +53 -53
  47. package/_site/_test/fresh-product-data-test.js +56 -56
  48. package/_site/_test/git-path-compatibility-test.js +80 -80
  49. package/_site/_test/git-validation-test.js +81 -81
  50. package/_site/_test/hook-worktree-test.js +42 -42
  51. package/_site/_test/index-builder-test.js +41 -41
  52. package/_site/_test/index-error-display-contract-test.js +10 -10
  53. package/_site/_test/integration-adapters-test.js +67 -67
  54. package/_site/_test/integration-capture-setup-test.js +118 -118
  55. package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
  56. package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
  57. package/_site/_test/integration-runtime-binding-test.js +473 -473
  58. package/_site/_test/integration-status-scan-test.js +29 -29
  59. package/_site/_test/kb-framework-test.js +57 -57
  60. package/_site/_test/kbpath-follow-test.js +63 -63
  61. package/_site/_test/knowledge-db-test.js +144 -144
  62. package/_site/_test/knowledge-engine-client-test.js +109 -109
  63. package/_site/_test/knowledge-maintenance-test.js +40 -40
  64. package/_site/_test/knowledge-panel-ui-contract-test.js +112 -0
  65. package/_site/_test/knowledge-scopes-test.js +37 -37
  66. package/_site/_test/knowledge-storage-location-test.js +28 -28
  67. package/_site/_test/launcher-readiness-test.js +80 -80
  68. package/_site/_test/legacy-claude-session-alias-test.js +106 -106
  69. package/_site/_test/legacy-routes-removed-test.js +47 -47
  70. package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
  71. package/_site/_test/live-gateway-test.js +48 -48
  72. package/_site/_test/local-marketplace-test.js +16 -16
  73. package/_site/_test/log-redaction-test.js +58 -58
  74. package/_site/_test/markdown-maintenance-test.js +11 -11
  75. package/_site/_test/model-context-windows-test.js +89 -89
  76. package/_site/_test/module-agent-start-proxy-test.js +65 -65
  77. package/_site/_test/module-artifact-integrity-test.js +27 -27
  78. package/_site/_test/module-boundary-test.js +30 -30
  79. package/_site/_test/module-bridge-eventbridge-test.js +202 -202
  80. package/_site/_test/module-dynamic-port-test.js +118 -118
  81. package/_site/_test/module-index-proxy-test.js +33 -33
  82. package/_site/_test/module-model-configuration-test.js +44 -44
  83. package/_site/_test/module-process-lifecycle-test.js +35 -35
  84. package/_site/_test/module-stream-proxy-test.js +34 -34
  85. package/_site/_test/module-terminal-restart-test.js +63 -63
  86. package/_site/_test/module-wiring-unbind-race-test.js +59 -59
  87. package/_site/_test/native-context-integrations-test.js +86 -86
  88. package/_site/_test/orphaned-claim-recovery-test.js +80 -80
  89. package/_site/_test/package-boundary-test.js +43 -42
  90. package/_site/_test/package-startup-test.js +56 -56
  91. package/_site/_test/packaged-ui-smoke-test.js +88 -88
  92. package/_site/_test/pr-consumer-contract-test.js +55 -55
  93. package/_site/_test/product-brand-contract-test.js +25 -25
  94. package/_site/_test/product-diagnostics-ui-test.js +93 -93
  95. package/_site/_test/product-import-ui-test.js +104 -104
  96. package/_site/_test/product-progress-status-test.js +12 -1
  97. package/_site/_test/project-remove-running-guard-test.js +79 -79
  98. package/_site/_test/project-store-test.js +90 -90
  99. package/_site/_test/registry-events-test.js +38 -38
  100. package/_site/_test/release-version-sync-test.js +115 -115
  101. package/_site/_test/requirement-binding-test.js +113 -113
  102. package/_site/_test/runtime-endpoint-test.js +64 -64
  103. package/_site/_test/scanner-test.js +83 -83
  104. package/_site/_test/server-security-test.js +96 -96
  105. package/_site/_test/sessions-stream-test.js +93 -93
  106. package/_site/_test/shared-scope-root-binding-test.js +67 -67
  107. package/_site/_test/simple-import-test.js +98 -98
  108. package/_site/_test/startup-analysis-disabled-test.js +6 -6
  109. package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
  110. package/_site/_test/team-knowledge-store-test.js +132 -132
  111. package/_site/_test/test-server-cleanup-test.js +35 -35
  112. package/_site/_test/test-server-module-isolation-test.js +37 -37
  113. package/_site/_test/tracking-start-test.js +35 -35
  114. package/_site/_test/workbench-context-receipts-test.js +22 -22
  115. package/_site/_test/workspace-ui-contract-test.js +31 -31
  116. package/_site/_test/zcode-capture-integration-test.js +108 -108
  117. package/_site/lib/ai-vendor-presets.js +83 -83
  118. package/_site/lib/ai-workspace.js +3 -3
  119. package/_site/lib/atomic-file.js +204 -204
  120. package/_site/lib/bridge-adapter.js +260 -260
  121. package/_site/lib/capture-hook-config.js +108 -108
  122. package/_site/lib/claude-cli-runner.js +89 -89
  123. package/_site/lib/cli-instances.js +43 -43
  124. package/_site/lib/codex-context-status.js +17 -17
  125. package/_site/lib/codex-notify-config.js +193 -193
  126. package/_site/lib/commit-conversation-binder.js +12 -12
  127. package/_site/lib/commit-reconciler.js +38 -38
  128. package/_site/lib/context-pack-builder.js +299 -299
  129. package/_site/lib/conversation-exclusions.js +204 -204
  130. package/_site/lib/conversation-query-service.js +17 -17
  131. package/_site/lib/data-dir.js +13 -13
  132. package/_site/lib/embedding-service.js +85 -85
  133. package/_site/lib/engine-connection.js +93 -93
  134. package/_site/lib/engine-knowledge-client.js +72 -72
  135. package/_site/lib/folder-picker-output.js +3 -3
  136. package/_site/lib/git-path.js +14 -14
  137. package/_site/lib/github-team-store.js +3 -3
  138. package/_site/lib/index-builder.js +157 -157
  139. package/_site/lib/integration-status-scan.js +99 -99
  140. package/_site/lib/integration-status-worker.js +14 -14
  141. package/_site/lib/kb-framework.js +15 -15
  142. package/_site/lib/knowledge-db.js +363 -363
  143. package/_site/lib/knowledge-schema.js +121 -121
  144. package/_site/lib/live-gateway.js +78 -78
  145. package/_site/lib/llm-client.js +12 -12
  146. package/_site/lib/model-context-windows.js +82 -82
  147. package/_site/lib/module-bridge.js +874 -874
  148. package/_site/lib/module-port-resolver.js +57 -57
  149. package/_site/lib/post-commit-automation.js +54 -54
  150. package/_site/lib/product-data.js +35 -35
  151. package/_site/lib/project-lifecycle-service.js +14 -14
  152. package/_site/lib/project-registry-store.js +129 -129
  153. package/_site/lib/prompt-registry.js +148 -148
  154. package/_site/lib/registry-events.js +16 -16
  155. package/_site/lib/requirement-adapters.js +10 -10
  156. package/_site/lib/requirement-binder.js +112 -112
  157. package/_site/lib/runtime-control.js +89 -89
  158. package/_site/lib/runtime-endpoint.js +5 -5
  159. package/_site/lib/zcode-context-integration.js +60 -60
  160. package/_site/scripts/codex-notify-fanout.cjs +95 -95
  161. package/_site/scripts/install-module-candidates.js +69 -69
  162. package/_site/scripts/pack-module-candidates.js +89 -89
  163. package/_site/scripts/sync-product-content.js +28 -28
  164. package/_site/scripts/sync-release-version.js +164 -164
  165. package/_site/scripts/vendor-modules.js +49 -49
  166. package/_site/scripts/verify-module-runtime.js +50 -50
  167. package/_site/scripts/verify-product-runtime.js +36 -36
  168. package/_site/scripts/verify-retrieval-gold.js +83 -83
  169. package/_site/server.js +15 -15
  170. package/_site/start.bat +26 -26
  171. package/_site/stop.bat +11 -11
  172. package/bin/project-knowledge-integrations.js +254 -254
  173. package/bin/project-knowledge-kb.js +67 -67
  174. package/bin/project-knowledge-mcp.js +8 -8
  175. package/bin/project-knowledge.js +170 -170
  176. package/claude-prompts.json +21 -21
  177. package/docs/README.zh-CN.md +205 -205
  178. package/docs/assets/architecture.svg +181 -181
  179. package/docs/assets/logo.svg +49 -49
  180. package/docs/fresh-v5-start.md +42 -42
  181. package/docs/leader-glm-workbench-playbook.md +122 -122
  182. package/docs/project-registry-schema.md +11 -11
  183. package/docs/release-5.0.16-acceptance.md +176 -176
  184. package/docs/release-5.0.17-acceptance.md +117 -117
  185. package/docs/release-5.0.19-acceptance.md +39 -25
  186. package/docs/team-knowledge-mode-a-plan.md +73 -73
  187. package/module-runtime-manifest.json +53 -33
  188. package/package.json +3 -2
  189. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  190. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  191. package/plugins/project-knowledge/.mcp.json +1 -1
  192. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  193. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  194. package/templates/change.md +30 -30
  195. package/templates/project-readme.md +18 -18
  196. package/ui/index.html +1 -1
  197. package/ui/knowledge-panel.html +118 -0
  198. package/ui/product.css +8 -0
  199. package/ui/product.js +146 -4
  200. package/vendor-manifest.json +55 -54
@@ -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
+ });