@sanqianx/ai-coding-event-bridge-console 0.7.2 → 0.7.4

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 (36) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +81 -81
  3. package/console/index.html +953 -953
  4. package/package.json +26 -26
  5. package/src/agent-detect.js +128 -128
  6. package/src/bridge/bin/commit-boundary.js +64 -64
  7. package/src/bridge/connectors/capture-guard.js +24 -24
  8. package/src/bridge/connectors/claude-code/hook-entry.js +45 -45
  9. package/src/bridge/connectors/claude-code/notify.js +57 -57
  10. package/src/bridge/connectors/codex/cursor-store.js +83 -83
  11. package/src/bridge/connectors/codex/hook-entry.js +215 -215
  12. package/src/bridge/connectors/codex/session-parser.js +100 -100
  13. package/src/bridge/connectors/opencode/hook-entry.js +55 -55
  14. package/src/bridge/core/bridge.js +249 -246
  15. package/src/bridge/core/commit-projection.js +288 -288
  16. package/src/bridge/core/compaction.js +23 -23
  17. package/src/bridge/core/consumer-registry.js +111 -111
  18. package/src/bridge/core/event-schema.js +127 -127
  19. package/src/bridge/core/fs-utils.js +81 -81
  20. package/src/bridge/core/journal-router.js +39 -39
  21. package/src/bridge/core/journal.js +507 -507
  22. package/src/bridge/core/lock.js +96 -96
  23. package/src/bridge/core/normalizer.js +114 -114
  24. package/src/bridge/core/paths.js +12 -12
  25. package/src/bridge/core/project-registry.js +176 -176
  26. package/src/bridge/core/repo-context.js +191 -191
  27. package/src/bridge/core/runtime-home.js +189 -189
  28. package/src/bridge/core/semver.js +23 -23
  29. package/src/bridge/core/turn-identity.js +127 -127
  30. package/src/bridge/index.js +86 -86
  31. package/src/bridge/installers/claude-code/installer.js +181 -181
  32. package/src/bridge/installers/codex/installer.js +114 -114
  33. package/src/bridge/installers/opencode/installer.js +96 -96
  34. package/src/bridge/installers/zcode/installer.js +200 -200
  35. package/src/bridge/query/conversation-query.js +194 -194
  36. package/src/server.js +825 -825
@@ -1,189 +1,189 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { bridgeHome } = require('./paths');
6
- const { CrossProcessLock, lockPathFor } = require('./lock');
7
- const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync, copyDirSync } = require('./fs-utils');
8
- const semver = require('./semver');
9
-
10
- // The shim is intentionally version-independent: AI client configs point at this
11
- // stable path forever, and it delegates to whatever runtime version is active.
12
- // It reads the client payload from stdin, forwards it to the active runtime and
13
- // always exits 0 — fail-open is a product invariant.
14
- const SHIM_SOURCE = `#!/usr/bin/env node
15
- 'use strict';
16
- const fs = require('fs');
17
- const path = require('path');
18
-
19
- function home() {
20
- return process.env.AI_CODING_EVENT_BRIDGE_HOME || path.resolve(__dirname, '..');
21
- }
22
-
23
- function readStdin() {
24
- return new Promise((resolve) => {
25
- let data = '';
26
- try {
27
- process.stdin.setEncoding('utf8');
28
- process.stdin.on('data', (chunk) => (data += chunk));
29
- process.stdin.on('end', () => resolve(data));
30
- process.stdin.on('error', () => resolve(data));
31
- } catch (_) {
32
- resolve(data);
33
- }
34
- });
35
- }
36
-
37
- (async () => {
38
- try {
39
- const raw = await readStdin();
40
- let payload = null;
41
- try {
42
- payload = JSON.parse(raw);
43
- } catch (_) {
44
- payload = null;
45
- }
46
- const active = JSON.parse(fs.readFileSync(path.join(home(), 'active-runtime.json'), 'utf8'));
47
- const hookPath = path.join(home(), 'runtime', String(active.version), 'hook.cjs');
48
- if (fs.existsSync(hookPath)) {
49
- const runtime = require(hookPath);
50
- if (typeof runtime.main === 'function') {
51
- await runtime.main({ payload, home: home(), argv: process.argv.slice(2), env: process.env });
52
- }
53
- }
54
- } catch (_) {
55
- // Fail open by design: a broken Bridge must never block the AI client.
56
- } finally {
57
- process.exit(0);
58
- }
59
- })();
60
- `;
61
-
62
- // The versioned runtime entry: dispatches the client payload into the
63
- // self-contained bridge tree materialized next to it. The capture-disable
64
- // environment marker is honored before any connector is even loaded.
65
- const RUNTIME_HOOK_SOURCE = `#!/usr/bin/env node
66
- 'use strict';
67
- async function main({ payload, home, argv, env }) {
68
- const effectiveEnv = env || process.env;
69
- if (effectiveEnv && effectiveEnv.AI_CODING_EVENT_BRIDGE_CAPTURE === '0') {
70
- return { status: 'ignored', reason: 'capture-disabled' };
71
- }
72
- if (payload && typeof payload === 'object') {
73
- // Claude Code ships hook_event_name (also seen as hookName/hook_name);
74
- // all three identify a Claude hook payload and MUST route to the Claude
75
- // connector — never fall through to Codex just because session_id exists.
76
- if (payload.hookName || payload.hook_event_name || payload.hook_name) {
77
- const claudeEntry = require('./bridge/connectors/claude-code/hook-entry.js');
78
- const sourceIndex = Array.isArray(argv) ? argv.indexOf('--source') : -1;
79
- const source = sourceIndex >= 0 && argv[sourceIndex + 1] === 'zcode' ? 'zcode' : 'claude-code';
80
- return claudeEntry.mainFailOpen({ home, payload, source, argv, env });
81
- }
82
- if (payload.type === 'user' || payload.type === 'assistant') {
83
- const opencodeEntry = require('./bridge/connectors/opencode/hook-entry.js');
84
- return opencodeEntry.mainFailOpen({ home, payload });
85
- }
86
- if (payload.session_id || payload.sessionId) {
87
- const codexEntry = require('./bridge/connectors/codex/hook-entry.js');
88
- return codexEntry.mainFailOpen({ home, payload });
89
- }
90
- }
91
- return { status: 'ignored' };
92
- }
93
- module.exports = { main };
94
- `;
95
-
96
- function materializeBridgeTree(runtimeDir) {
97
- const srcRoot = path.join(__dirname, '..');
98
- copyDirSync(path.join(srcRoot, 'core'), path.join(runtimeDir, 'bridge', 'core'));
99
- copyDirSync(path.join(srcRoot, 'connectors'), path.join(runtimeDir, 'bridge', 'connectors'));
100
- }
101
-
102
- function writeTextAtomicSync(file, content) {
103
- const tmp = `${file}.tmp`;
104
- const fd = fs.openSync(tmp, 'w');
105
- try {
106
- fs.writeSync(fd, content);
107
- fs.fsyncSync(fd);
108
- } finally {
109
- fs.closeSync(fd);
110
- }
111
- fs.renameSync(tmp, file);
112
- }
113
-
114
- async function ensureRuntimeHome({ homeDir, version } = {}) {
115
- const home = bridgeHome(homeDir);
116
- const requestedVersion = version || require('../../../package.json').version;
117
- const lock = new CrossProcessLock(lockPathFor(home, 'install.lock'));
118
- return lock.withLock(async () => {
119
- ensureDirSync(path.join(home, 'bin'));
120
- ensureDirSync(path.join(home, 'runtime'));
121
- ensureDirSync(path.join(home, 'journal'));
122
- ensureDirSync(path.join(home, 'locks'));
123
-
124
- const shimPath = path.join(home, 'bin', 'bridge-hook.cjs');
125
- writeTextAtomicSync(shimPath, SHIM_SOURCE);
126
-
127
- const runtimeDir = path.join(home, 'runtime', requestedVersion);
128
- const activeFile = path.join(home, 'active-runtime.json');
129
- const active = readJsonIfExists(activeFile);
130
-
131
- const materializeRuntime = () => {
132
- ensureDirSync(runtimeDir);
133
- writeTextAtomicSync(path.join(runtimeDir, 'hook.cjs'), RUNTIME_HOOK_SOURCE);
134
- materializeBridgeTree(runtimeDir);
135
- if (!fs.existsSync(path.join(runtimeDir, 'manifest.json'))) {
136
- writeJsonAtomicSync(path.join(runtimeDir, 'manifest.json'), {
137
- version: requestedVersion,
138
- createdAt: new Date().toISOString()
139
- });
140
- }
141
- };
142
-
143
- let action;
144
- if (!active || !active.version) {
145
- materializeRuntime();
146
- writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
147
- action = 'activated';
148
- } else {
149
- const cmp = semver.compare(active.version, requestedVersion);
150
- if (cmp === null) {
151
- materializeRuntime();
152
- writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
153
- action = 'activated-unparseable-active';
154
- } else if (cmp === 0) {
155
- materializeRuntime();
156
- action = 'noop-same-version';
157
- } else if (cmp < 0) {
158
- const activeMajor = semver.parse(active.version).major;
159
- const requestedMajor = semver.parse(requestedVersion).major;
160
- if (activeMajor !== requestedMajor) {
161
- return {
162
- home,
163
- shimPath,
164
- conflict: true,
165
- activeVersion: active.version,
166
- requestedVersion,
167
- action: 'conflict-major-mismatch'
168
- };
169
- }
170
- materializeRuntime();
171
- writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
172
- action = 'upgraded';
173
- } else {
174
- action = 'kept-newer';
175
- }
176
- }
177
-
178
- return {
179
- home,
180
- shimPath,
181
- conflict: false,
182
- activeVersion: readJsonIfExists(activeFile).version,
183
- requestedVersion,
184
- action
185
- };
186
- });
187
- }
188
-
189
- module.exports = { ensureRuntimeHome, SHIM_SOURCE };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { bridgeHome } = require('./paths');
6
+ const { CrossProcessLock, lockPathFor } = require('./lock');
7
+ const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync, copyDirSync } = require('./fs-utils');
8
+ const semver = require('./semver');
9
+
10
+ // The shim is intentionally version-independent: AI client configs point at this
11
+ // stable path forever, and it delegates to whatever runtime version is active.
12
+ // It reads the client payload from stdin, forwards it to the active runtime and
13
+ // always exits 0 — fail-open is a product invariant.
14
+ const SHIM_SOURCE = `#!/usr/bin/env node
15
+ 'use strict';
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ function home() {
20
+ return process.env.AI_CODING_EVENT_BRIDGE_HOME || path.resolve(__dirname, '..');
21
+ }
22
+
23
+ function readStdin() {
24
+ return new Promise((resolve) => {
25
+ let data = '';
26
+ try {
27
+ process.stdin.setEncoding('utf8');
28
+ process.stdin.on('data', (chunk) => (data += chunk));
29
+ process.stdin.on('end', () => resolve(data));
30
+ process.stdin.on('error', () => resolve(data));
31
+ } catch (_) {
32
+ resolve(data);
33
+ }
34
+ });
35
+ }
36
+
37
+ (async () => {
38
+ try {
39
+ const raw = await readStdin();
40
+ let payload = null;
41
+ try {
42
+ payload = JSON.parse(raw);
43
+ } catch (_) {
44
+ payload = null;
45
+ }
46
+ const active = JSON.parse(fs.readFileSync(path.join(home(), 'active-runtime.json'), 'utf8'));
47
+ const hookPath = path.join(home(), 'runtime', String(active.version), 'hook.cjs');
48
+ if (fs.existsSync(hookPath)) {
49
+ const runtime = require(hookPath);
50
+ if (typeof runtime.main === 'function') {
51
+ await runtime.main({ payload, home: home(), argv: process.argv.slice(2), env: process.env });
52
+ }
53
+ }
54
+ } catch (_) {
55
+ // Fail open by design: a broken Bridge must never block the AI client.
56
+ } finally {
57
+ process.exit(0);
58
+ }
59
+ })();
60
+ `;
61
+
62
+ // The versioned runtime entry: dispatches the client payload into the
63
+ // self-contained bridge tree materialized next to it. The capture-disable
64
+ // environment marker is honored before any connector is even loaded.
65
+ const RUNTIME_HOOK_SOURCE = `#!/usr/bin/env node
66
+ 'use strict';
67
+ async function main({ payload, home, argv, env }) {
68
+ const effectiveEnv = env || process.env;
69
+ if (effectiveEnv && effectiveEnv.AI_CODING_EVENT_BRIDGE_CAPTURE === '0') {
70
+ return { status: 'ignored', reason: 'capture-disabled' };
71
+ }
72
+ if (payload && typeof payload === 'object') {
73
+ // Claude Code ships hook_event_name (also seen as hookName/hook_name);
74
+ // all three identify a Claude hook payload and MUST route to the Claude
75
+ // connector — never fall through to Codex just because session_id exists.
76
+ if (payload.hookName || payload.hook_event_name || payload.hook_name) {
77
+ const claudeEntry = require('./bridge/connectors/claude-code/hook-entry.js');
78
+ const sourceIndex = Array.isArray(argv) ? argv.indexOf('--source') : -1;
79
+ const source = sourceIndex >= 0 && argv[sourceIndex + 1] === 'zcode' ? 'zcode' : 'claude-code';
80
+ return claudeEntry.mainFailOpen({ home, payload, source, argv, env });
81
+ }
82
+ if (payload.type === 'user' || payload.type === 'assistant') {
83
+ const opencodeEntry = require('./bridge/connectors/opencode/hook-entry.js');
84
+ return opencodeEntry.mainFailOpen({ home, payload });
85
+ }
86
+ if (payload.session_id || payload.sessionId) {
87
+ const codexEntry = require('./bridge/connectors/codex/hook-entry.js');
88
+ return codexEntry.mainFailOpen({ home, payload });
89
+ }
90
+ }
91
+ return { status: 'ignored' };
92
+ }
93
+ module.exports = { main };
94
+ `;
95
+
96
+ function materializeBridgeTree(runtimeDir) {
97
+ const srcRoot = path.join(__dirname, '..');
98
+ copyDirSync(path.join(srcRoot, 'core'), path.join(runtimeDir, 'bridge', 'core'));
99
+ copyDirSync(path.join(srcRoot, 'connectors'), path.join(runtimeDir, 'bridge', 'connectors'));
100
+ }
101
+
102
+ function writeTextAtomicSync(file, content) {
103
+ const tmp = `${file}.tmp`;
104
+ const fd = fs.openSync(tmp, 'w');
105
+ try {
106
+ fs.writeSync(fd, content);
107
+ fs.fsyncSync(fd);
108
+ } finally {
109
+ fs.closeSync(fd);
110
+ }
111
+ fs.renameSync(tmp, file);
112
+ }
113
+
114
+ async function ensureRuntimeHome({ homeDir, version } = {}) {
115
+ const home = bridgeHome(homeDir);
116
+ const requestedVersion = version || require('../../../package.json').version;
117
+ const lock = new CrossProcessLock(lockPathFor(home, 'install.lock'));
118
+ return lock.withLock(async () => {
119
+ ensureDirSync(path.join(home, 'bin'));
120
+ ensureDirSync(path.join(home, 'runtime'));
121
+ ensureDirSync(path.join(home, 'journal'));
122
+ ensureDirSync(path.join(home, 'locks'));
123
+
124
+ const shimPath = path.join(home, 'bin', 'bridge-hook.cjs');
125
+ writeTextAtomicSync(shimPath, SHIM_SOURCE);
126
+
127
+ const runtimeDir = path.join(home, 'runtime', requestedVersion);
128
+ const activeFile = path.join(home, 'active-runtime.json');
129
+ const active = readJsonIfExists(activeFile);
130
+
131
+ const materializeRuntime = () => {
132
+ ensureDirSync(runtimeDir);
133
+ writeTextAtomicSync(path.join(runtimeDir, 'hook.cjs'), RUNTIME_HOOK_SOURCE);
134
+ materializeBridgeTree(runtimeDir);
135
+ if (!fs.existsSync(path.join(runtimeDir, 'manifest.json'))) {
136
+ writeJsonAtomicSync(path.join(runtimeDir, 'manifest.json'), {
137
+ version: requestedVersion,
138
+ createdAt: new Date().toISOString()
139
+ });
140
+ }
141
+ };
142
+
143
+ let action;
144
+ if (!active || !active.version) {
145
+ materializeRuntime();
146
+ writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
147
+ action = 'activated';
148
+ } else {
149
+ const cmp = semver.compare(active.version, requestedVersion);
150
+ if (cmp === null) {
151
+ materializeRuntime();
152
+ writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
153
+ action = 'activated-unparseable-active';
154
+ } else if (cmp === 0) {
155
+ materializeRuntime();
156
+ action = 'noop-same-version';
157
+ } else if (cmp < 0) {
158
+ const activeMajor = semver.parse(active.version).major;
159
+ const requestedMajor = semver.parse(requestedVersion).major;
160
+ if (activeMajor !== requestedMajor) {
161
+ return {
162
+ home,
163
+ shimPath,
164
+ conflict: true,
165
+ activeVersion: active.version,
166
+ requestedVersion,
167
+ action: 'conflict-major-mismatch'
168
+ };
169
+ }
170
+ materializeRuntime();
171
+ writeJsonAtomicSync(activeFile, { version: requestedVersion, activatedAt: new Date().toISOString() });
172
+ action = 'upgraded';
173
+ } else {
174
+ action = 'kept-newer';
175
+ }
176
+ }
177
+
178
+ return {
179
+ home,
180
+ shimPath,
181
+ conflict: false,
182
+ activeVersion: readJsonIfExists(activeFile).version,
183
+ requestedVersion,
184
+ action
185
+ };
186
+ });
187
+ }
188
+
189
+ module.exports = { ensureRuntimeHome, SHIM_SOURCE };
@@ -1,23 +1,23 @@
1
- 'use strict';
2
-
3
- function parse(version) {
4
- const match = /^(\d+)\.(\d+)\.(\d+)/.exec(String(version));
5
- if (!match) return null;
6
- return {
7
- major: Number(match[1]),
8
- minor: Number(match[2]),
9
- patch: Number(match[3])
10
- };
11
- }
12
-
13
- function compare(a, b) {
14
- const pa = parse(a);
15
- const pb = parse(b);
16
- if (!pa || !pb) return null;
17
- if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;
18
- if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;
19
- if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;
20
- return 0;
21
- }
22
-
23
- module.exports = { parse, compare };
1
+ 'use strict';
2
+
3
+ function parse(version) {
4
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(String(version));
5
+ if (!match) return null;
6
+ return {
7
+ major: Number(match[1]),
8
+ minor: Number(match[2]),
9
+ patch: Number(match[3])
10
+ };
11
+ }
12
+
13
+ function compare(a, b) {
14
+ const pa = parse(a);
15
+ const pb = parse(b);
16
+ if (!pa || !pb) return null;
17
+ if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;
18
+ if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;
19
+ if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;
20
+ return 0;
21
+ }
22
+
23
+ module.exports = { parse, compare };