@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.
- package/LICENSE +21 -21
- package/README.md +81 -74
- package/console/index.html +953 -948
- package/package.json +26 -26
- package/src/agent-detect.js +128 -128
- package/src/bin.js +80 -80
- 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 -188
- 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 -188
- package/src/bridge/query/conversation-query.js +194 -194
- package/src/server.js +825 -825
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync } = require('./fs-utils');
|
|
6
|
-
const { CrossProcessLock } = require('./lock');
|
|
7
|
-
const { resolveRepoContext, comparisonForm, repoIdentityKey } = require('./repo-context');
|
|
8
|
-
|
|
9
|
-
const REGISTRY_VERSION = 1;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Imported-project registry, persisted at `<home>/projects.json`. A registered
|
|
13
|
-
* project owns a durable journal at `<store>/journal`; capture routes events
|
|
14
|
-
* whose repoIdentity.workspaceId matches the project id there instead of the
|
|
15
|
-
* global journal. Removing a project only unregisters it — its data on disk is
|
|
16
|
-
* never touched.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
function registryFile(home) {
|
|
20
|
-
return path.join(home, 'projects.json');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function registryLockFile(home) {
|
|
24
|
-
return path.join(home, 'locks', 'projects.lock');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function loadRegistry(home) {
|
|
28
|
-
const raw = readJsonIfExists(registryFile(home));
|
|
29
|
-
if (!raw || !Array.isArray(raw.projects)) {
|
|
30
|
-
return { version: REGISTRY_VERSION, projects: [] };
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
version: raw.version || REGISTRY_VERSION,
|
|
34
|
-
projects: raw.projects.filter(
|
|
35
|
-
(project) => project && typeof project === 'object' && typeof project.id === 'string' && typeof project.store === 'string'
|
|
36
|
-
)
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function sameComparisonPath(a, b) {
|
|
41
|
-
return comparisonForm(a) === comparisonForm(b);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function defaultStoreFor(home, name) {
|
|
45
|
-
return path.join(home, 'projects', name);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function addProject({ home, path: projectDir, store, resolveRepo = resolveRepoContext } = {}) {
|
|
49
|
-
if (typeof home !== 'string' || !home) {
|
|
50
|
-
throw new Error('addProject requires home');
|
|
51
|
-
}
|
|
52
|
-
if (typeof projectDir !== 'string' || !projectDir.trim()) {
|
|
53
|
-
const err = new Error('addProject requires a project directory');
|
|
54
|
-
err.code = 'PROJECT_PATH_REQUIRED';
|
|
55
|
-
throw err;
|
|
56
|
-
}
|
|
57
|
-
let stat = null;
|
|
58
|
-
try {
|
|
59
|
-
stat = fs.statSync(projectDir);
|
|
60
|
-
} catch (_) {
|
|
61
|
-
stat = null;
|
|
62
|
-
}
|
|
63
|
-
if (!stat || !stat.isDirectory()) {
|
|
64
|
-
const err = new Error(`project directory does not exist: ${projectDir}`);
|
|
65
|
-
err.code = 'PROJECT_DIR_INVALID';
|
|
66
|
-
throw err;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// The registry only accepts Git work trees: event identity (workspaceId) is
|
|
70
|
-
// derived from the Git toplevel, so a non-Git folder could never match the
|
|
71
|
-
// events captured inside it. Importing a subdirectory resolves to its repo
|
|
72
|
-
// root, which is the identity events actually carry.
|
|
73
|
-
const repo = await resolveRepo(projectDir);
|
|
74
|
-
if (!repo || !repo.repoIdentity) {
|
|
75
|
-
const err = new Error(`not a Git work tree (no repo identity): ${projectDir}`);
|
|
76
|
-
err.code = 'PROJECT_NOT_GIT';
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const name = path.basename(repo.projectPath) || repo.projectPath;
|
|
81
|
-
const entry = {
|
|
82
|
-
id: repo.repoIdentity.workspaceId,
|
|
83
|
-
name,
|
|
84
|
-
path: repo.projectPath,
|
|
85
|
-
remote: repo.repoIdentity.remote || null,
|
|
86
|
-
store: typeof store === 'string' && store.trim() ? path.resolve(store) : defaultStoreFor(home, name),
|
|
87
|
-
createdAt: new Date().toISOString()
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const lock = new CrossProcessLock(registryLockFile(home));
|
|
91
|
-
return lock.withLock(async () => {
|
|
92
|
-
const registry = loadRegistry(home);
|
|
93
|
-
if (registry.projects.some((project) => project.id === entry.id)) {
|
|
94
|
-
const err = new Error(`project already imported: ${entry.path}`);
|
|
95
|
-
err.code = 'PROJECT_DUPLICATE';
|
|
96
|
-
throw err;
|
|
97
|
-
}
|
|
98
|
-
if (registry.projects.some((project) => sameComparisonPath(project.path, entry.path))) {
|
|
99
|
-
const err = new Error(`project path already imported: ${entry.path}`);
|
|
100
|
-
err.code = 'PROJECT_DUPLICATE';
|
|
101
|
-
throw err;
|
|
102
|
-
}
|
|
103
|
-
if (registry.projects.some((project) => sameComparisonPath(project.store, entry.store))) {
|
|
104
|
-
const err = new Error(`store location already used by another project: ${entry.store}`);
|
|
105
|
-
err.code = 'PROJECT_STORE_CONFLICT';
|
|
106
|
-
throw err;
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
ensureDirSync(path.join(entry.store, 'journal'));
|
|
110
|
-
} catch (_) {
|
|
111
|
-
const err = new Error(`cannot create journal directory at: ${entry.store}`);
|
|
112
|
-
err.code = 'PROJECT_STORE_INVALID';
|
|
113
|
-
throw err;
|
|
114
|
-
}
|
|
115
|
-
writeJsonAtomicSync(registryFile(home), {
|
|
116
|
-
version: REGISTRY_VERSION,
|
|
117
|
-
projects: registry.projects.concat([entry])
|
|
118
|
-
});
|
|
119
|
-
return { ...entry };
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function removeProject({ home, id } = {}) {
|
|
124
|
-
if (typeof home !== 'string' || !home) {
|
|
125
|
-
throw new Error('removeProject requires home');
|
|
126
|
-
}
|
|
127
|
-
if (typeof id !== 'string' || !id) {
|
|
128
|
-
const err = new Error('removeProject requires a project id');
|
|
129
|
-
err.code = 'PROJECT_ID_REQUIRED';
|
|
130
|
-
throw err;
|
|
131
|
-
}
|
|
132
|
-
const lock = new CrossProcessLock(registryLockFile(home));
|
|
133
|
-
return lock.withLock(async () => {
|
|
134
|
-
const registry = loadRegistry(home);
|
|
135
|
-
let removed = null;
|
|
136
|
-
const projects = [];
|
|
137
|
-
for (const project of registry.projects) {
|
|
138
|
-
if (project.id === id) {
|
|
139
|
-
removed = project;
|
|
140
|
-
} else {
|
|
141
|
-
projects.push(project);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (!removed) {
|
|
145
|
-
const err = new Error(`project not registered: ${id}`);
|
|
146
|
-
err.code = 'PROJECT_NOT_FOUND';
|
|
147
|
-
throw err;
|
|
148
|
-
}
|
|
149
|
-
writeJsonAtomicSync(registryFile(home), { version: REGISTRY_VERSION, projects });
|
|
150
|
-
return { ...removed };
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function listProjects(home) {
|
|
155
|
-
return loadRegistry(home).projects.map((project) => ({ ...project }));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Registry lookup by capture identity (repoIdentity.workspaceId, or the
|
|
159
|
-
// identity string itself for legacy records): the same rule journal routing
|
|
160
|
-
// uses to pick a project's journal.
|
|
161
|
-
function findProject(home, repoIdentity) {
|
|
162
|
-
const key = repoIdentityKey(repoIdentity);
|
|
163
|
-
if (!key) return null;
|
|
164
|
-
const project = loadRegistry(home).projects.find((candidate) => candidate.id === key);
|
|
165
|
-
return project ? { ...project } : null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
module.exports = {
|
|
169
|
-
REGISTRY_VERSION,
|
|
170
|
-
registryFile,
|
|
171
|
-
loadRegistry,
|
|
172
|
-
addProject,
|
|
173
|
-
removeProject,
|
|
174
|
-
listProjects,
|
|
175
|
-
findProject
|
|
176
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { writeJsonAtomicSync, readJsonIfExists, ensureDirSync } = require('./fs-utils');
|
|
6
|
+
const { CrossProcessLock } = require('./lock');
|
|
7
|
+
const { resolveRepoContext, comparisonForm, repoIdentityKey } = require('./repo-context');
|
|
8
|
+
|
|
9
|
+
const REGISTRY_VERSION = 1;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Imported-project registry, persisted at `<home>/projects.json`. A registered
|
|
13
|
+
* project owns a durable journal at `<store>/journal`; capture routes events
|
|
14
|
+
* whose repoIdentity.workspaceId matches the project id there instead of the
|
|
15
|
+
* global journal. Removing a project only unregisters it — its data on disk is
|
|
16
|
+
* never touched.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function registryFile(home) {
|
|
20
|
+
return path.join(home, 'projects.json');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function registryLockFile(home) {
|
|
24
|
+
return path.join(home, 'locks', 'projects.lock');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadRegistry(home) {
|
|
28
|
+
const raw = readJsonIfExists(registryFile(home));
|
|
29
|
+
if (!raw || !Array.isArray(raw.projects)) {
|
|
30
|
+
return { version: REGISTRY_VERSION, projects: [] };
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
version: raw.version || REGISTRY_VERSION,
|
|
34
|
+
projects: raw.projects.filter(
|
|
35
|
+
(project) => project && typeof project === 'object' && typeof project.id === 'string' && typeof project.store === 'string'
|
|
36
|
+
)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sameComparisonPath(a, b) {
|
|
41
|
+
return comparisonForm(a) === comparisonForm(b);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function defaultStoreFor(home, name) {
|
|
45
|
+
return path.join(home, 'projects', name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function addProject({ home, path: projectDir, store, resolveRepo = resolveRepoContext } = {}) {
|
|
49
|
+
if (typeof home !== 'string' || !home) {
|
|
50
|
+
throw new Error('addProject requires home');
|
|
51
|
+
}
|
|
52
|
+
if (typeof projectDir !== 'string' || !projectDir.trim()) {
|
|
53
|
+
const err = new Error('addProject requires a project directory');
|
|
54
|
+
err.code = 'PROJECT_PATH_REQUIRED';
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
let stat = null;
|
|
58
|
+
try {
|
|
59
|
+
stat = fs.statSync(projectDir);
|
|
60
|
+
} catch (_) {
|
|
61
|
+
stat = null;
|
|
62
|
+
}
|
|
63
|
+
if (!stat || !stat.isDirectory()) {
|
|
64
|
+
const err = new Error(`project directory does not exist: ${projectDir}`);
|
|
65
|
+
err.code = 'PROJECT_DIR_INVALID';
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The registry only accepts Git work trees: event identity (workspaceId) is
|
|
70
|
+
// derived from the Git toplevel, so a non-Git folder could never match the
|
|
71
|
+
// events captured inside it. Importing a subdirectory resolves to its repo
|
|
72
|
+
// root, which is the identity events actually carry.
|
|
73
|
+
const repo = await resolveRepo(projectDir);
|
|
74
|
+
if (!repo || !repo.repoIdentity) {
|
|
75
|
+
const err = new Error(`not a Git work tree (no repo identity): ${projectDir}`);
|
|
76
|
+
err.code = 'PROJECT_NOT_GIT';
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const name = path.basename(repo.projectPath) || repo.projectPath;
|
|
81
|
+
const entry = {
|
|
82
|
+
id: repo.repoIdentity.workspaceId,
|
|
83
|
+
name,
|
|
84
|
+
path: repo.projectPath,
|
|
85
|
+
remote: repo.repoIdentity.remote || null,
|
|
86
|
+
store: typeof store === 'string' && store.trim() ? path.resolve(store) : defaultStoreFor(home, name),
|
|
87
|
+
createdAt: new Date().toISOString()
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const lock = new CrossProcessLock(registryLockFile(home));
|
|
91
|
+
return lock.withLock(async () => {
|
|
92
|
+
const registry = loadRegistry(home);
|
|
93
|
+
if (registry.projects.some((project) => project.id === entry.id)) {
|
|
94
|
+
const err = new Error(`project already imported: ${entry.path}`);
|
|
95
|
+
err.code = 'PROJECT_DUPLICATE';
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
if (registry.projects.some((project) => sameComparisonPath(project.path, entry.path))) {
|
|
99
|
+
const err = new Error(`project path already imported: ${entry.path}`);
|
|
100
|
+
err.code = 'PROJECT_DUPLICATE';
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
if (registry.projects.some((project) => sameComparisonPath(project.store, entry.store))) {
|
|
104
|
+
const err = new Error(`store location already used by another project: ${entry.store}`);
|
|
105
|
+
err.code = 'PROJECT_STORE_CONFLICT';
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
ensureDirSync(path.join(entry.store, 'journal'));
|
|
110
|
+
} catch (_) {
|
|
111
|
+
const err = new Error(`cannot create journal directory at: ${entry.store}`);
|
|
112
|
+
err.code = 'PROJECT_STORE_INVALID';
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
writeJsonAtomicSync(registryFile(home), {
|
|
116
|
+
version: REGISTRY_VERSION,
|
|
117
|
+
projects: registry.projects.concat([entry])
|
|
118
|
+
});
|
|
119
|
+
return { ...entry };
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function removeProject({ home, id } = {}) {
|
|
124
|
+
if (typeof home !== 'string' || !home) {
|
|
125
|
+
throw new Error('removeProject requires home');
|
|
126
|
+
}
|
|
127
|
+
if (typeof id !== 'string' || !id) {
|
|
128
|
+
const err = new Error('removeProject requires a project id');
|
|
129
|
+
err.code = 'PROJECT_ID_REQUIRED';
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
const lock = new CrossProcessLock(registryLockFile(home));
|
|
133
|
+
return lock.withLock(async () => {
|
|
134
|
+
const registry = loadRegistry(home);
|
|
135
|
+
let removed = null;
|
|
136
|
+
const projects = [];
|
|
137
|
+
for (const project of registry.projects) {
|
|
138
|
+
if (project.id === id) {
|
|
139
|
+
removed = project;
|
|
140
|
+
} else {
|
|
141
|
+
projects.push(project);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!removed) {
|
|
145
|
+
const err = new Error(`project not registered: ${id}`);
|
|
146
|
+
err.code = 'PROJECT_NOT_FOUND';
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
writeJsonAtomicSync(registryFile(home), { version: REGISTRY_VERSION, projects });
|
|
150
|
+
return { ...removed };
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function listProjects(home) {
|
|
155
|
+
return loadRegistry(home).projects.map((project) => ({ ...project }));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Registry lookup by capture identity (repoIdentity.workspaceId, or the
|
|
159
|
+
// identity string itself for legacy records): the same rule journal routing
|
|
160
|
+
// uses to pick a project's journal.
|
|
161
|
+
function findProject(home, repoIdentity) {
|
|
162
|
+
const key = repoIdentityKey(repoIdentity);
|
|
163
|
+
if (!key) return null;
|
|
164
|
+
const project = loadRegistry(home).projects.find((candidate) => candidate.id === key);
|
|
165
|
+
return project ? { ...project } : null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
REGISTRY_VERSION,
|
|
170
|
+
registryFile,
|
|
171
|
+
loadRegistry,
|
|
172
|
+
addProject,
|
|
173
|
+
removeProject,
|
|
174
|
+
listProjects,
|
|
175
|
+
findProject
|
|
176
|
+
};
|