@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.
- package/LICENSE +21 -21
- package/README.md +81 -81
- package/console/index.html +953 -953
- package/package.json +26 -26
- package/src/agent-detect.js +128 -128
- package/src/bridge/bin/commit-boundary.js +64 -64
- package/src/bridge/connectors/capture-guard.js +24 -24
- package/src/bridge/connectors/claude-code/hook-entry.js +45 -45
- package/src/bridge/connectors/claude-code/notify.js +57 -57
- package/src/bridge/connectors/codex/cursor-store.js +83 -83
- package/src/bridge/connectors/codex/hook-entry.js +215 -215
- package/src/bridge/connectors/codex/session-parser.js +100 -100
- package/src/bridge/connectors/opencode/hook-entry.js +55 -55
- package/src/bridge/core/bridge.js +249 -246
- package/src/bridge/core/commit-projection.js +288 -288
- package/src/bridge/core/compaction.js +23 -23
- package/src/bridge/core/consumer-registry.js +111 -111
- package/src/bridge/core/event-schema.js +127 -127
- package/src/bridge/core/fs-utils.js +81 -81
- package/src/bridge/core/journal-router.js +39 -39
- package/src/bridge/core/journal.js +507 -507
- package/src/bridge/core/lock.js +96 -96
- package/src/bridge/core/normalizer.js +114 -114
- package/src/bridge/core/paths.js +12 -12
- package/src/bridge/core/project-registry.js +176 -176
- package/src/bridge/core/repo-context.js +191 -191
- package/src/bridge/core/runtime-home.js +189 -189
- package/src/bridge/core/semver.js +23 -23
- package/src/bridge/core/turn-identity.js +127 -127
- package/src/bridge/index.js +86 -86
- package/src/bridge/installers/claude-code/installer.js +181 -181
- package/src/bridge/installers/codex/installer.js +114 -114
- package/src/bridge/installers/opencode/installer.js +96 -96
- package/src/bridge/installers/zcode/installer.js +200 -200
- package/src/bridge/query/conversation-query.js +194 -194
- package/src/server.js +825 -825
package/src/bridge/core/lock.js
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { ensureDirSync } = require('./fs-utils');
|
|
6
|
-
|
|
7
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
-
|
|
9
|
-
class LockError extends Error {
|
|
10
|
-
constructor(message) {
|
|
11
|
-
super(message);
|
|
12
|
-
this.name = 'LockError';
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class CrossProcessLock {
|
|
17
|
-
constructor(lockFile, { staleMs = 30000, pollMs = 15, timeoutMs = 60000 } = {}) {
|
|
18
|
-
this.lockFile = lockFile;
|
|
19
|
-
this.staleMs = staleMs;
|
|
20
|
-
this.pollMs = pollMs;
|
|
21
|
-
this.timeoutMs = timeoutMs;
|
|
22
|
-
this._held = false;
|
|
23
|
-
this._chain = Promise.resolve();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async acquire() {
|
|
27
|
-
if (this._held) throw new LockError('lock already held by this handle');
|
|
28
|
-
// Materialize the lock directory on first real use so that merely
|
|
29
|
-
// constructing a registry/journal never creates a Bridge home.
|
|
30
|
-
ensureDirSync(path.dirname(this.lockFile));
|
|
31
|
-
const deadline = Date.now() + this.timeoutMs;
|
|
32
|
-
for (;;) {
|
|
33
|
-
try {
|
|
34
|
-
const fd = fs.openSync(this.lockFile, 'wx');
|
|
35
|
-
try {
|
|
36
|
-
fs.writeSync(fd, `${JSON.stringify({ pid: process.pid, at: Date.now() })}\n`);
|
|
37
|
-
} finally {
|
|
38
|
-
fs.closeSync(fd);
|
|
39
|
-
}
|
|
40
|
-
this._held = true;
|
|
41
|
-
return;
|
|
42
|
-
} catch (err) {
|
|
43
|
-
// EEXIST: lock file present. EPERM/EBUSY: Windows reports these when
|
|
44
|
-
// another process holds the file without a sharing mode — still "locked".
|
|
45
|
-
if (!err || !['EEXIST', 'EPERM', 'EBUSY'].includes(err.code)) throw err;
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
const st = fs.statSync(this.lockFile);
|
|
49
|
-
if (Date.now() - st.mtimeMs > this.staleMs) {
|
|
50
|
-
fs.unlinkSync(this.lockFile);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
} catch (_) {
|
|
54
|
-
// Lock vanished between open failure and stat; retry immediately.
|
|
55
|
-
}
|
|
56
|
-
if (Date.now() > deadline) {
|
|
57
|
-
throw new LockError(`timed out acquiring lock ${this.lockFile}`);
|
|
58
|
-
}
|
|
59
|
-
await sleep(this.pollMs);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
release() {
|
|
64
|
-
if (!this._held) return;
|
|
65
|
-
this._held = false;
|
|
66
|
-
try {
|
|
67
|
-
// A lock is one file, never a directory. unlink also avoids Node 24's
|
|
68
|
-
// Windows rm(force) no-op on some non-ASCII filenames.
|
|
69
|
-
fs.unlinkSync(this.lockFile);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
if (!error || error.code !== 'ENOENT') throw error;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async withLock(fn) {
|
|
76
|
-
let release = null;
|
|
77
|
-
const run = async () => {
|
|
78
|
-
await this.acquire();
|
|
79
|
-
release = () => this.release();
|
|
80
|
-
try {
|
|
81
|
-
return await fn();
|
|
82
|
-
} finally {
|
|
83
|
-
release();
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
const result = this._chain.then(run, run);
|
|
87
|
-
this._chain = result.then(() => undefined, () => undefined);
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function lockPathFor(homeDir, name) {
|
|
93
|
-
return path.join(homeDir, 'locks', name);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
module.exports = { CrossProcessLock, lockPathFor, LockError };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { ensureDirSync } = require('./fs-utils');
|
|
6
|
+
|
|
7
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
+
|
|
9
|
+
class LockError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'LockError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class CrossProcessLock {
|
|
17
|
+
constructor(lockFile, { staleMs = 30000, pollMs = 15, timeoutMs = 60000 } = {}) {
|
|
18
|
+
this.lockFile = lockFile;
|
|
19
|
+
this.staleMs = staleMs;
|
|
20
|
+
this.pollMs = pollMs;
|
|
21
|
+
this.timeoutMs = timeoutMs;
|
|
22
|
+
this._held = false;
|
|
23
|
+
this._chain = Promise.resolve();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async acquire() {
|
|
27
|
+
if (this._held) throw new LockError('lock already held by this handle');
|
|
28
|
+
// Materialize the lock directory on first real use so that merely
|
|
29
|
+
// constructing a registry/journal never creates a Bridge home.
|
|
30
|
+
ensureDirSync(path.dirname(this.lockFile));
|
|
31
|
+
const deadline = Date.now() + this.timeoutMs;
|
|
32
|
+
for (;;) {
|
|
33
|
+
try {
|
|
34
|
+
const fd = fs.openSync(this.lockFile, 'wx');
|
|
35
|
+
try {
|
|
36
|
+
fs.writeSync(fd, `${JSON.stringify({ pid: process.pid, at: Date.now() })}\n`);
|
|
37
|
+
} finally {
|
|
38
|
+
fs.closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
this._held = true;
|
|
41
|
+
return;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
// EEXIST: lock file present. EPERM/EBUSY: Windows reports these when
|
|
44
|
+
// another process holds the file without a sharing mode — still "locked".
|
|
45
|
+
if (!err || !['EEXIST', 'EPERM', 'EBUSY'].includes(err.code)) throw err;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const st = fs.statSync(this.lockFile);
|
|
49
|
+
if (Date.now() - st.mtimeMs > this.staleMs) {
|
|
50
|
+
fs.unlinkSync(this.lockFile);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
} catch (_) {
|
|
54
|
+
// Lock vanished between open failure and stat; retry immediately.
|
|
55
|
+
}
|
|
56
|
+
if (Date.now() > deadline) {
|
|
57
|
+
throw new LockError(`timed out acquiring lock ${this.lockFile}`);
|
|
58
|
+
}
|
|
59
|
+
await sleep(this.pollMs);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
release() {
|
|
64
|
+
if (!this._held) return;
|
|
65
|
+
this._held = false;
|
|
66
|
+
try {
|
|
67
|
+
// A lock is one file, never a directory. unlink also avoids Node 24's
|
|
68
|
+
// Windows rm(force) no-op on some non-ASCII filenames.
|
|
69
|
+
fs.unlinkSync(this.lockFile);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (!error || error.code !== 'ENOENT') throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async withLock(fn) {
|
|
76
|
+
let release = null;
|
|
77
|
+
const run = async () => {
|
|
78
|
+
await this.acquire();
|
|
79
|
+
release = () => this.release();
|
|
80
|
+
try {
|
|
81
|
+
return await fn();
|
|
82
|
+
} finally {
|
|
83
|
+
release();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const result = this._chain.then(run, run);
|
|
87
|
+
this._chain = result.then(() => undefined, () => undefined);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function lockPathFor(homeDir, name) {
|
|
93
|
+
return path.join(homeDir, 'locks', name);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { CrossProcessLock, lockPathFor, LockError };
|
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { randomUUID } = require('crypto');
|
|
4
|
-
const { validateAndNormalizeEvent } = require('./event-schema');
|
|
5
|
-
|
|
6
|
-
function assignTurnIdForPrompt() {
|
|
7
|
-
// A real captured user prompt owns a fresh turn identity. This is identity
|
|
8
|
-
// assignment for captured evidence, never content fabrication.
|
|
9
|
-
return `turn_${randomUUID()}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Claude Code hook payload -> normalized event.
|
|
14
|
-
* Claude Code ships the event name as hook_event_name; hookName/hook_name
|
|
15
|
-
* variants are accepted for compatibility. UserPromptSubmit carries the exact
|
|
16
|
-
* user prompt; Stop yields an assistant_response only when
|
|
17
|
-
* last_assistant_message exists — an empty Stop is not conversation evidence
|
|
18
|
-
* and produces no event. Turn closure binding happens in the journal's
|
|
19
|
-
* canonical append path.
|
|
20
|
-
*/
|
|
21
|
-
function hookEventName(raw) {
|
|
22
|
-
return raw.hook_event_name || raw.hookName || raw.hook_name || null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function normalizeClaudeCode(raw, source = 'claude-code') {
|
|
26
|
-
if (!raw || typeof raw !== 'object') return null;
|
|
27
|
-
const base = { source: source === 'zcode' ? 'zcode' : 'claude-code' };
|
|
28
|
-
if (hookEventName(raw) === 'UserPromptSubmit' || raw.eventType === 'user_prompt') {
|
|
29
|
-
const content = typeof raw.prompt === 'string' ? raw.prompt : null;
|
|
30
|
-
const event = {
|
|
31
|
-
...base,
|
|
32
|
-
eventType: 'user_prompt',
|
|
33
|
-
role: 'user',
|
|
34
|
-
content,
|
|
35
|
-
sessionId: raw.session_id || null,
|
|
36
|
-
turnId: raw.turnId || assignTurnIdForPrompt(),
|
|
37
|
-
repoIdentity: raw.repoIdentity,
|
|
38
|
-
projectPath: raw.cwd,
|
|
39
|
-
identityConfidence: content === null ? 'partial' : 'exact',
|
|
40
|
-
captureStatus: content === null ? 'partial' : 'complete',
|
|
41
|
-
rawEventType: hookEventName(raw) || 'user_prompt'
|
|
42
|
-
};
|
|
43
|
-
return validateAndNormalizeEvent(event);
|
|
44
|
-
}
|
|
45
|
-
if (hookEventName(raw) === 'Stop' || raw.eventType === 'assistant_response') {
|
|
46
|
-
if (typeof raw.last_assistant_message !== 'string' || !raw.last_assistant_message) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
return validateAndNormalizeEvent({
|
|
50
|
-
...base,
|
|
51
|
-
eventType: 'assistant_response',
|
|
52
|
-
role: 'assistant',
|
|
53
|
-
content: raw.last_assistant_message,
|
|
54
|
-
sessionId: raw.session_id || null,
|
|
55
|
-
turnId: raw.turnId || null,
|
|
56
|
-
repoIdentity: raw.repoIdentity,
|
|
57
|
-
projectPath: raw.cwd,
|
|
58
|
-
identityConfidence: 'partial',
|
|
59
|
-
captureStatus: 'complete',
|
|
60
|
-
rawEventType: hookEventName(raw) || 'assistant_response'
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Codex events arrive via notify + session parsing (BR05 owns the parser).
|
|
68
|
-
* The normalizer only maps a parsed turn record to the standard event shape.
|
|
69
|
-
*/
|
|
70
|
-
function normalizeCodex(raw) {
|
|
71
|
-
if (!raw || typeof raw !== 'object') return null;
|
|
72
|
-
if (raw.eventType === 'user_prompt' || raw.eventType === 'assistant_response') {
|
|
73
|
-
return validateAndNormalizeEvent({
|
|
74
|
-
source: 'codex',
|
|
75
|
-
eventType: raw.eventType,
|
|
76
|
-
role: raw.eventType === 'user_prompt' ? 'user' : 'assistant',
|
|
77
|
-
content: typeof raw.content === 'string' ? raw.content : null,
|
|
78
|
-
sessionId: raw.sessionId || null,
|
|
79
|
-
turnId: raw.turnId || (raw.eventType === 'user_prompt' ? assignTurnIdForPrompt() : null),
|
|
80
|
-
repoIdentity: raw.repoIdentity,
|
|
81
|
-
projectPath: raw.projectPath || raw.cwd,
|
|
82
|
-
identityConfidence: raw.identityConfidence,
|
|
83
|
-
captureStatus: raw.captureStatus,
|
|
84
|
-
rawEventType: raw.rawEventType
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* OpenCode plugin events: user/assistant lifecycle from the official plugin.
|
|
92
|
-
*/
|
|
93
|
-
function normalizeOpenCode(raw) {
|
|
94
|
-
if (!raw || typeof raw !== 'object') return null;
|
|
95
|
-
if (raw.type === 'user' || raw.type === 'assistant') {
|
|
96
|
-
const isUser = raw.type === 'user';
|
|
97
|
-
return validateAndNormalizeEvent({
|
|
98
|
-
source: 'opencode',
|
|
99
|
-
eventType: isUser ? 'user_prompt' : 'assistant_response',
|
|
100
|
-
role: isUser ? 'user' : 'assistant',
|
|
101
|
-
content: typeof raw.text === 'string' ? raw.text : null,
|
|
102
|
-
sessionId: raw.sessionId || null,
|
|
103
|
-
turnId: isUser ? (raw.turnId || assignTurnIdForPrompt()) : raw.turnId || null,
|
|
104
|
-
repoIdentity: raw.repoIdentity,
|
|
105
|
-
projectPath: raw.cwd || raw.projectPath,
|
|
106
|
-
identityConfidence: raw.identityConfidence,
|
|
107
|
-
captureStatus: raw.captureStatus,
|
|
108
|
-
rawEventType: raw.type
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
module.exports = { normalizeClaudeCode, normalizeCodex, normalizeOpenCode };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require('crypto');
|
|
4
|
+
const { validateAndNormalizeEvent } = require('./event-schema');
|
|
5
|
+
|
|
6
|
+
function assignTurnIdForPrompt() {
|
|
7
|
+
// A real captured user prompt owns a fresh turn identity. This is identity
|
|
8
|
+
// assignment for captured evidence, never content fabrication.
|
|
9
|
+
return `turn_${randomUUID()}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Claude Code hook payload -> normalized event.
|
|
14
|
+
* Claude Code ships the event name as hook_event_name; hookName/hook_name
|
|
15
|
+
* variants are accepted for compatibility. UserPromptSubmit carries the exact
|
|
16
|
+
* user prompt; Stop yields an assistant_response only when
|
|
17
|
+
* last_assistant_message exists — an empty Stop is not conversation evidence
|
|
18
|
+
* and produces no event. Turn closure binding happens in the journal's
|
|
19
|
+
* canonical append path.
|
|
20
|
+
*/
|
|
21
|
+
function hookEventName(raw) {
|
|
22
|
+
return raw.hook_event_name || raw.hookName || raw.hook_name || null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeClaudeCode(raw, source = 'claude-code') {
|
|
26
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
27
|
+
const base = { source: source === 'zcode' ? 'zcode' : 'claude-code' };
|
|
28
|
+
if (hookEventName(raw) === 'UserPromptSubmit' || raw.eventType === 'user_prompt') {
|
|
29
|
+
const content = typeof raw.prompt === 'string' ? raw.prompt : null;
|
|
30
|
+
const event = {
|
|
31
|
+
...base,
|
|
32
|
+
eventType: 'user_prompt',
|
|
33
|
+
role: 'user',
|
|
34
|
+
content,
|
|
35
|
+
sessionId: raw.session_id || null,
|
|
36
|
+
turnId: raw.turnId || assignTurnIdForPrompt(),
|
|
37
|
+
repoIdentity: raw.repoIdentity,
|
|
38
|
+
projectPath: raw.cwd,
|
|
39
|
+
identityConfidence: content === null ? 'partial' : 'exact',
|
|
40
|
+
captureStatus: content === null ? 'partial' : 'complete',
|
|
41
|
+
rawEventType: hookEventName(raw) || 'user_prompt'
|
|
42
|
+
};
|
|
43
|
+
return validateAndNormalizeEvent(event);
|
|
44
|
+
}
|
|
45
|
+
if (hookEventName(raw) === 'Stop' || raw.eventType === 'assistant_response') {
|
|
46
|
+
if (typeof raw.last_assistant_message !== 'string' || !raw.last_assistant_message) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return validateAndNormalizeEvent({
|
|
50
|
+
...base,
|
|
51
|
+
eventType: 'assistant_response',
|
|
52
|
+
role: 'assistant',
|
|
53
|
+
content: raw.last_assistant_message,
|
|
54
|
+
sessionId: raw.session_id || null,
|
|
55
|
+
turnId: raw.turnId || null,
|
|
56
|
+
repoIdentity: raw.repoIdentity,
|
|
57
|
+
projectPath: raw.cwd,
|
|
58
|
+
identityConfidence: 'partial',
|
|
59
|
+
captureStatus: 'complete',
|
|
60
|
+
rawEventType: hookEventName(raw) || 'assistant_response'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Codex events arrive via notify + session parsing (BR05 owns the parser).
|
|
68
|
+
* The normalizer only maps a parsed turn record to the standard event shape.
|
|
69
|
+
*/
|
|
70
|
+
function normalizeCodex(raw) {
|
|
71
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
72
|
+
if (raw.eventType === 'user_prompt' || raw.eventType === 'assistant_response') {
|
|
73
|
+
return validateAndNormalizeEvent({
|
|
74
|
+
source: 'codex',
|
|
75
|
+
eventType: raw.eventType,
|
|
76
|
+
role: raw.eventType === 'user_prompt' ? 'user' : 'assistant',
|
|
77
|
+
content: typeof raw.content === 'string' ? raw.content : null,
|
|
78
|
+
sessionId: raw.sessionId || null,
|
|
79
|
+
turnId: raw.turnId || (raw.eventType === 'user_prompt' ? assignTurnIdForPrompt() : null),
|
|
80
|
+
repoIdentity: raw.repoIdentity,
|
|
81
|
+
projectPath: raw.projectPath || raw.cwd,
|
|
82
|
+
identityConfidence: raw.identityConfidence,
|
|
83
|
+
captureStatus: raw.captureStatus,
|
|
84
|
+
rawEventType: raw.rawEventType
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* OpenCode plugin events: user/assistant lifecycle from the official plugin.
|
|
92
|
+
*/
|
|
93
|
+
function normalizeOpenCode(raw) {
|
|
94
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
95
|
+
if (raw.type === 'user' || raw.type === 'assistant') {
|
|
96
|
+
const isUser = raw.type === 'user';
|
|
97
|
+
return validateAndNormalizeEvent({
|
|
98
|
+
source: 'opencode',
|
|
99
|
+
eventType: isUser ? 'user_prompt' : 'assistant_response',
|
|
100
|
+
role: isUser ? 'user' : 'assistant',
|
|
101
|
+
content: typeof raw.text === 'string' ? raw.text : null,
|
|
102
|
+
sessionId: raw.sessionId || null,
|
|
103
|
+
turnId: isUser ? (raw.turnId || assignTurnIdForPrompt()) : raw.turnId || null,
|
|
104
|
+
repoIdentity: raw.repoIdentity,
|
|
105
|
+
projectPath: raw.cwd || raw.projectPath,
|
|
106
|
+
identityConfidence: raw.identityConfidence,
|
|
107
|
+
captureStatus: raw.captureStatus,
|
|
108
|
+
rawEventType: raw.type
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { normalizeClaudeCode, normalizeCodex, normalizeOpenCode };
|
package/src/bridge/core/paths.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
function bridgeHome(explicit) {
|
|
7
|
-
if (explicit) return explicit;
|
|
8
|
-
if (process.env.AI_CODING_EVENT_BRIDGE_HOME) return process.env.AI_CODING_EVENT_BRIDGE_HOME;
|
|
9
|
-
return path.join(os.homedir(), '.ai-coding-event-bridge');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
module.exports = { bridgeHome };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function bridgeHome(explicit) {
|
|
7
|
+
if (explicit) return explicit;
|
|
8
|
+
if (process.env.AI_CODING_EVENT_BRIDGE_HOME) return process.env.AI_CODING_EVENT_BRIDGE_HOME;
|
|
9
|
+
return path.join(os.homedir(), '.ai-coding-event-bridge');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { bridgeHome };
|