@sanqianx/ai-coding-event-bridge-console 0.7.2 → 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 -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/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 -189
  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 -200
  36. package/src/bridge/query/conversation-query.js +194 -194
  37. 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
+ };