@sanqianx/ai-coding-event-bridge-console 0.7.1 → 0.7.3

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