@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,191 +1,191 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const crypto = require('crypto');
6
- const { execFile } = require('child_process');
7
-
8
- const REPO_IDENTITY_SCHEMA = 'repo-identity/v1';
9
-
10
- function normalizeGitUrl(url) {
11
- if (typeof url !== 'string') return null;
12
- let u = url.trim();
13
- if (!u) return null;
14
- let match = /^git@([^:]+):(.+?)(?:\.git)?$/i.exec(u);
15
- if (match) return `${match[1].toLowerCase()}/${match[2].replace(/\.git$/, '')}`;
16
- match = /^(?:https?|ssh|git):\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?$/i.exec(u);
17
- if (match) return `${match[1].toLowerCase()}/${match[2].replace(/\.git$/, '')}`;
18
- return u.replace(/\.git$/, '');
19
- }
20
-
21
- function runGitDefault(args, cwd) {
22
- return new Promise((resolve, reject) => {
23
- execFile('git', args, { cwd, windowsHide: true }, (err, stdout) => {
24
- if (err) return reject(err);
25
- resolve(String(stdout).trim());
26
- });
27
- });
28
- }
29
-
30
- function realpathBestEffort(target) {
31
- try {
32
- return fs.realpathSync.native ? fs.realpathSync.native(target) : fs.realpathSync(target);
33
- } catch (_) {
34
- return null;
35
- }
36
- }
37
-
38
- // Comparison form: identity comparisons are case-insensitive on Windows and
39
- // separator-insensitive everywhere. The display workspaceRoot keeps its
40
- // on-disk casing; only the hash/comparison form is normalized.
41
- function comparisonForm(target) {
42
- let value = String(target).replace(/\\/g, '/');
43
- while (value.length > 1 && value.endsWith('/')) value = value.slice(0, -1);
44
- if (process.platform === 'win32') value = value.toLowerCase();
45
- return value;
46
- }
47
-
48
- function workspaceIdFor(workspaceRoot) {
49
- const digest = crypto.createHash('sha256').update(comparisonForm(workspaceRoot)).digest('hex');
50
- return `sha256:${digest}`;
51
- }
52
-
53
- function isValidRepoIdentityV1(identity) {
54
- return Boolean(
55
- identity &&
56
- typeof identity === 'object' &&
57
- identity.schema === REPO_IDENTITY_SCHEMA &&
58
- typeof identity.workspaceId === 'string' &&
59
- /^sha256:[0-9a-f]{64}$/.test(identity.workspaceId) &&
60
- typeof identity.workspaceRoot === 'string' &&
61
- identity.workspaceRoot.length > 0 &&
62
- (identity.commonDir === null || identity.commonDir === undefined || typeof identity.commonDir === 'string') &&
63
- (identity.remote === null || identity.remote === undefined || typeof identity.remote === 'string')
64
- );
65
- }
66
-
67
- // Journal/projection key. v1 identities key by workspaceId; plain strings are
68
- // legacy journal records written before repo-identity/v1 and stay readable.
69
- function repoIdentityKey(identity) {
70
- if (identity === null || identity === undefined) return null;
71
- if (typeof identity === 'string') return identity;
72
- if (isValidRepoIdentityV1(identity)) return identity.workspaceId;
73
- return null;
74
- }
75
-
76
- // Turn-namespace key: events with no repo identity at all share the 'no-repo'
77
- // namespace so same-session binding still works outside Git repos, while
78
- // malformed identity objects get a unique namespace and never cross-bind.
79
- function turnScopeKey(identity) {
80
- if (identity === null || identity === undefined) return 'no-repo';
81
- const key = repoIdentityKey(identity);
82
- if (key !== null) return key;
83
- try {
84
- return `invalid:${JSON.stringify(identity).slice(0, 64)}`;
85
- } catch (_) {
86
- return 'invalid:unserializable';
87
- }
88
- }
89
-
90
- function sameRepoIdentity(a, b) {
91
- const keyA = repoIdentityKey(a);
92
- const keyB = repoIdentityKey(b);
93
- if (keyA === null || keyB === null) return false;
94
- return keyA === keyB;
95
- }
96
-
97
- function buildRepoIdentityV1({ workspaceRoot, commonDir = null, remote = null }) {
98
- return {
99
- schema: REPO_IDENTITY_SCHEMA,
100
- workspaceId: workspaceIdFor(workspaceRoot),
101
- workspaceRoot,
102
- commonDir: commonDir === undefined ? null : commonDir,
103
- remote: remote === undefined ? null : remote
104
- };
105
- }
106
-
107
- /**
108
- * Canonical workspace identity for an authoritative development cwd.
109
- *
110
- * 1. `git rev-parse --show-toplevel` (authoritative Git working tree root)
111
- * 2. realpath + canonical separators for the display root
112
- * 3. workspaceId = sha256 over the comparison form (case-insensitive on win32)
113
- * 4. commonDir from `git rev-parse --git-common-dir`, resolved against cwd
114
- * (metadata: worktrees share it, but their workspaceIds differ)
115
- * 5. normalized origin URL as metadata only — never the primary identity
116
- *
117
- * Non-Git cwd => repoIdentity null + identityConfidence 'unavailable'.
118
- * No fallback to process.cwd, no fake workspaceId, remote never overrides.
119
- */
120
- async function resolveRepoContext(cwd, { runGit = runGitDefault } = {}) {
121
- const projectPath = realpathBestEffort(cwd) || path.resolve(cwd);
122
- let toplevel = null;
123
- try {
124
- toplevel = await runGit(['rev-parse', '--show-toplevel'], cwd);
125
- } catch (_) {
126
- return {
127
- repoIdentity: null,
128
- projectPath,
129
- branch: null,
130
- headAtCapture: null,
131
- identityConfidence: 'unavailable'
132
- };
133
- }
134
- const realToplevel = path.resolve(realpathBestEffort(toplevel) || toplevel);
135
-
136
- let commonDir = null;
137
- try {
138
- const rawCommonDir = String(await runGit(['rev-parse', '--git-common-dir'], cwd)).trim();
139
- if (rawCommonDir && !/[\r\n\0]/.test(rawCommonDir) && !rawCommonDir.startsWith('--')) {
140
- const absoluteCommonDir = path.resolve(cwd, rawCommonDir);
141
- commonDir = realpathBestEffort(absoluteCommonDir) || absoluteCommonDir;
142
- }
143
- } catch (_) {
144
- commonDir = null;
145
- }
146
-
147
- let origin = null;
148
- try {
149
- origin = await runGit(['config', '--get', 'remote.origin.url'], cwd);
150
- } catch (_) {
151
- origin = null;
152
- }
153
- let branch = null;
154
- try {
155
- branch = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
156
- } catch (_) {
157
- branch = null;
158
- }
159
- let head = null;
160
- try {
161
- head = await runGit(['rev-parse', 'HEAD'], cwd);
162
- } catch (_) {
163
- head = null;
164
- }
165
-
166
- return {
167
- repoIdentity: buildRepoIdentityV1({
168
- workspaceRoot: realToplevel,
169
- commonDir,
170
- remote: normalizeGitUrl(origin)
171
- }),
172
- projectPath: realToplevel,
173
- branch: branch === 'HEAD' ? null : branch,
174
- headAtCapture: head,
175
- identityConfidence: 'exact'
176
- };
177
- }
178
-
179
- module.exports = {
180
- REPO_IDENTITY_SCHEMA,
181
- normalizeGitUrl,
182
- resolveRepoContext,
183
- realpathBestEffort,
184
- comparisonForm,
185
- workspaceIdFor,
186
- buildRepoIdentityV1,
187
- isValidRepoIdentityV1,
188
- repoIdentityKey,
189
- turnScopeKey,
190
- sameRepoIdentity
191
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const { execFile } = require('child_process');
7
+
8
+ const REPO_IDENTITY_SCHEMA = 'repo-identity/v1';
9
+
10
+ function normalizeGitUrl(url) {
11
+ if (typeof url !== 'string') return null;
12
+ let u = url.trim();
13
+ if (!u) return null;
14
+ let match = /^git@([^:]+):(.+?)(?:\.git)?$/i.exec(u);
15
+ if (match) return `${match[1].toLowerCase()}/${match[2].replace(/\.git$/, '')}`;
16
+ match = /^(?:https?|ssh|git):\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?$/i.exec(u);
17
+ if (match) return `${match[1].toLowerCase()}/${match[2].replace(/\.git$/, '')}`;
18
+ return u.replace(/\.git$/, '');
19
+ }
20
+
21
+ function runGitDefault(args, cwd) {
22
+ return new Promise((resolve, reject) => {
23
+ execFile('git', args, { cwd, windowsHide: true }, (err, stdout) => {
24
+ if (err) return reject(err);
25
+ resolve(String(stdout).trim());
26
+ });
27
+ });
28
+ }
29
+
30
+ function realpathBestEffort(target) {
31
+ try {
32
+ return fs.realpathSync.native ? fs.realpathSync.native(target) : fs.realpathSync(target);
33
+ } catch (_) {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ // Comparison form: identity comparisons are case-insensitive on Windows and
39
+ // separator-insensitive everywhere. The display workspaceRoot keeps its
40
+ // on-disk casing; only the hash/comparison form is normalized.
41
+ function comparisonForm(target) {
42
+ let value = String(target).replace(/\\/g, '/');
43
+ while (value.length > 1 && value.endsWith('/')) value = value.slice(0, -1);
44
+ if (process.platform === 'win32') value = value.toLowerCase();
45
+ return value;
46
+ }
47
+
48
+ function workspaceIdFor(workspaceRoot) {
49
+ const digest = crypto.createHash('sha256').update(comparisonForm(workspaceRoot)).digest('hex');
50
+ return `sha256:${digest}`;
51
+ }
52
+
53
+ function isValidRepoIdentityV1(identity) {
54
+ return Boolean(
55
+ identity &&
56
+ typeof identity === 'object' &&
57
+ identity.schema === REPO_IDENTITY_SCHEMA &&
58
+ typeof identity.workspaceId === 'string' &&
59
+ /^sha256:[0-9a-f]{64}$/.test(identity.workspaceId) &&
60
+ typeof identity.workspaceRoot === 'string' &&
61
+ identity.workspaceRoot.length > 0 &&
62
+ (identity.commonDir === null || identity.commonDir === undefined || typeof identity.commonDir === 'string') &&
63
+ (identity.remote === null || identity.remote === undefined || typeof identity.remote === 'string')
64
+ );
65
+ }
66
+
67
+ // Journal/projection key. v1 identities key by workspaceId; plain strings are
68
+ // legacy journal records written before repo-identity/v1 and stay readable.
69
+ function repoIdentityKey(identity) {
70
+ if (identity === null || identity === undefined) return null;
71
+ if (typeof identity === 'string') return identity;
72
+ if (isValidRepoIdentityV1(identity)) return identity.workspaceId;
73
+ return null;
74
+ }
75
+
76
+ // Turn-namespace key: events with no repo identity at all share the 'no-repo'
77
+ // namespace so same-session binding still works outside Git repos, while
78
+ // malformed identity objects get a unique namespace and never cross-bind.
79
+ function turnScopeKey(identity) {
80
+ if (identity === null || identity === undefined) return 'no-repo';
81
+ const key = repoIdentityKey(identity);
82
+ if (key !== null) return key;
83
+ try {
84
+ return `invalid:${JSON.stringify(identity).slice(0, 64)}`;
85
+ } catch (_) {
86
+ return 'invalid:unserializable';
87
+ }
88
+ }
89
+
90
+ function sameRepoIdentity(a, b) {
91
+ const keyA = repoIdentityKey(a);
92
+ const keyB = repoIdentityKey(b);
93
+ if (keyA === null || keyB === null) return false;
94
+ return keyA === keyB;
95
+ }
96
+
97
+ function buildRepoIdentityV1({ workspaceRoot, commonDir = null, remote = null }) {
98
+ return {
99
+ schema: REPO_IDENTITY_SCHEMA,
100
+ workspaceId: workspaceIdFor(workspaceRoot),
101
+ workspaceRoot,
102
+ commonDir: commonDir === undefined ? null : commonDir,
103
+ remote: remote === undefined ? null : remote
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Canonical workspace identity for an authoritative development cwd.
109
+ *
110
+ * 1. `git rev-parse --show-toplevel` (authoritative Git working tree root)
111
+ * 2. realpath + canonical separators for the display root
112
+ * 3. workspaceId = sha256 over the comparison form (case-insensitive on win32)
113
+ * 4. commonDir from `git rev-parse --git-common-dir`, resolved against cwd
114
+ * (metadata: worktrees share it, but their workspaceIds differ)
115
+ * 5. normalized origin URL as metadata only — never the primary identity
116
+ *
117
+ * Non-Git cwd => repoIdentity null + identityConfidence 'unavailable'.
118
+ * No fallback to process.cwd, no fake workspaceId, remote never overrides.
119
+ */
120
+ async function resolveRepoContext(cwd, { runGit = runGitDefault } = {}) {
121
+ const projectPath = realpathBestEffort(cwd) || path.resolve(cwd);
122
+ let toplevel = null;
123
+ try {
124
+ toplevel = await runGit(['rev-parse', '--show-toplevel'], cwd);
125
+ } catch (_) {
126
+ return {
127
+ repoIdentity: null,
128
+ projectPath,
129
+ branch: null,
130
+ headAtCapture: null,
131
+ identityConfidence: 'unavailable'
132
+ };
133
+ }
134
+ const realToplevel = path.resolve(realpathBestEffort(toplevel) || toplevel);
135
+
136
+ let commonDir = null;
137
+ try {
138
+ const rawCommonDir = String(await runGit(['rev-parse', '--git-common-dir'], cwd)).trim();
139
+ if (rawCommonDir && !/[\r\n\0]/.test(rawCommonDir) && !rawCommonDir.startsWith('--')) {
140
+ const absoluteCommonDir = path.resolve(cwd, rawCommonDir);
141
+ commonDir = realpathBestEffort(absoluteCommonDir) || absoluteCommonDir;
142
+ }
143
+ } catch (_) {
144
+ commonDir = null;
145
+ }
146
+
147
+ let origin = null;
148
+ try {
149
+ origin = await runGit(['config', '--get', 'remote.origin.url'], cwd);
150
+ } catch (_) {
151
+ origin = null;
152
+ }
153
+ let branch = null;
154
+ try {
155
+ branch = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
156
+ } catch (_) {
157
+ branch = null;
158
+ }
159
+ let head = null;
160
+ try {
161
+ head = await runGit(['rev-parse', 'HEAD'], cwd);
162
+ } catch (_) {
163
+ head = null;
164
+ }
165
+
166
+ return {
167
+ repoIdentity: buildRepoIdentityV1({
168
+ workspaceRoot: realToplevel,
169
+ commonDir,
170
+ remote: normalizeGitUrl(origin)
171
+ }),
172
+ projectPath: realToplevel,
173
+ branch: branch === 'HEAD' ? null : branch,
174
+ headAtCapture: head,
175
+ identityConfidence: 'exact'
176
+ };
177
+ }
178
+
179
+ module.exports = {
180
+ REPO_IDENTITY_SCHEMA,
181
+ normalizeGitUrl,
182
+ resolveRepoContext,
183
+ realpathBestEffort,
184
+ comparisonForm,
185
+ workspaceIdFor,
186
+ buildRepoIdentityV1,
187
+ isValidRepoIdentityV1,
188
+ repoIdentityKey,
189
+ turnScopeKey,
190
+ sameRepoIdentity
191
+ };