@wibeco/bridge 0.2.6 → 0.2.8
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.
|
@@ -15,7 +15,14 @@ var hookEventKindSchema = z.enum([
|
|
|
15
15
|
"shell.completed",
|
|
16
16
|
"mcp.started",
|
|
17
17
|
"mcp.completed",
|
|
18
|
+
"response.completed",
|
|
18
19
|
"progress.shared",
|
|
20
|
+
"git.branch_changed",
|
|
21
|
+
"git.commit_created",
|
|
22
|
+
"git.push_completed",
|
|
23
|
+
"git.merge_completed",
|
|
24
|
+
"git.rebase_completed",
|
|
25
|
+
"git.stash_created",
|
|
19
26
|
"unknown"
|
|
20
27
|
]);
|
|
21
28
|
var safeScalarSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
@@ -156,7 +163,7 @@ var events = {
|
|
|
156
163
|
"pre-tool-use": "tool.started",
|
|
157
164
|
"post-tool-use": "tool.completed",
|
|
158
165
|
"post-tool-use-failure": "tool.completed",
|
|
159
|
-
notification: "
|
|
166
|
+
notification: "unknown"
|
|
160
167
|
};
|
|
161
168
|
var safeKeys = [
|
|
162
169
|
"hook_event_name",
|
|
@@ -171,12 +178,16 @@ var safeKeys = [
|
|
|
171
178
|
"session_started_at",
|
|
172
179
|
"heartbeat_at",
|
|
173
180
|
"heartbeat_interval_ms",
|
|
174
|
-
"session_elapsed_ms"
|
|
181
|
+
"session_elapsed_ms",
|
|
182
|
+
"title"
|
|
175
183
|
];
|
|
176
184
|
function mapClaudeCodeHook(eventName, input) {
|
|
177
185
|
const payload = objectPayload(input);
|
|
178
186
|
const toolInput = objectPayload(payload.tool_input);
|
|
179
187
|
const sessionId = safeIdentifier(payload.session_id);
|
|
188
|
+
const title = safeIdentifier(
|
|
189
|
+
payload.session_name ?? payload.title ?? payload.name
|
|
190
|
+
);
|
|
180
191
|
const model = safeModel(payload.model);
|
|
181
192
|
const path = safeRelativePath(
|
|
182
193
|
toolInput.file_path ?? payload.file_path,
|
|
@@ -191,6 +202,7 @@ function mapClaudeCodeHook(eventName, input) {
|
|
|
191
202
|
{
|
|
192
203
|
...payload,
|
|
193
204
|
...sessionId ? { session_id: sessionId } : {},
|
|
205
|
+
...title ? { title } : {},
|
|
194
206
|
...model ? { model } : {},
|
|
195
207
|
...path ? { path } : {},
|
|
196
208
|
...duration !== void 0 ? { duration_ms: duration } : {},
|
|
@@ -220,8 +232,8 @@ var events2 = {
|
|
|
220
232
|
"user-prompt-submit": "lifecycle.before",
|
|
221
233
|
"pre-tool-use": "tool.started",
|
|
222
234
|
"post-tool-use": "tool.completed",
|
|
223
|
-
"subagent-start": "
|
|
224
|
-
"subagent-stop": "
|
|
235
|
+
"subagent-start": "unknown",
|
|
236
|
+
"subagent-stop": "unknown",
|
|
225
237
|
stop: "lifecycle.after"
|
|
226
238
|
};
|
|
227
239
|
var safeKeys2 = [
|
|
@@ -237,7 +249,8 @@ var safeKeys2 = [
|
|
|
237
249
|
"session_started_at",
|
|
238
250
|
"heartbeat_at",
|
|
239
251
|
"heartbeat_interval_ms",
|
|
240
|
-
"session_elapsed_ms"
|
|
252
|
+
"session_elapsed_ms",
|
|
253
|
+
"title"
|
|
241
254
|
];
|
|
242
255
|
function mapCodexHook(eventName, input) {
|
|
243
256
|
const payload = objectPayload(input);
|
|
@@ -245,6 +258,9 @@ function mapCodexHook(eventName, input) {
|
|
|
245
258
|
const threadId = safeIdentifier(
|
|
246
259
|
payload["thread-id"] ?? payload.thread_id ?? payload.session_id
|
|
247
260
|
);
|
|
261
|
+
const title = safeIdentifier(
|
|
262
|
+
payload["thread-name"] ?? payload.thread_name ?? payload.title
|
|
263
|
+
);
|
|
248
264
|
const turnId = safeIdentifier(payload["turn-id"] ?? payload.turn_id);
|
|
249
265
|
const model = safeModel(payload.model);
|
|
250
266
|
const path = safeRelativePath(
|
|
@@ -257,6 +273,7 @@ function mapCodexHook(eventName, input) {
|
|
|
257
273
|
{
|
|
258
274
|
...payload,
|
|
259
275
|
...threadId ? { session_id: threadId } : {},
|
|
276
|
+
...title ? { title } : {},
|
|
260
277
|
...turnId ? { turn_id: turnId } : {},
|
|
261
278
|
...model ? { model } : {},
|
|
262
279
|
...path ? { path } : {},
|
|
@@ -296,11 +313,15 @@ var safeKeys3 = [
|
|
|
296
313
|
"session_started_at",
|
|
297
314
|
"heartbeat_at",
|
|
298
315
|
"heartbeat_interval_ms",
|
|
299
|
-
"session_elapsed_ms"
|
|
316
|
+
"session_elapsed_ms",
|
|
317
|
+
"title"
|
|
300
318
|
];
|
|
301
319
|
function mapCursorHook(eventName, input) {
|
|
302
320
|
const payload = objectPayload(input);
|
|
303
321
|
const sessionId = safeIdentifier(payload.conversation_id ?? payload.session_id);
|
|
322
|
+
const title = safeIdentifier(
|
|
323
|
+
payload.conversation_name ?? payload.title ?? payload.name
|
|
324
|
+
);
|
|
304
325
|
const model = safeModel(payload.model);
|
|
305
326
|
const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.find(
|
|
306
327
|
(root) => typeof root === "string"
|
|
@@ -315,6 +336,7 @@ function mapCursorHook(eventName, input) {
|
|
|
315
336
|
{
|
|
316
337
|
...payload,
|
|
317
338
|
...sessionId ? { session_id: sessionId } : {},
|
|
339
|
+
...title ? { title } : {},
|
|
318
340
|
...model ? { model } : {},
|
|
319
341
|
...path ? { path } : {},
|
|
320
342
|
...typeof payload.lines_added === "number" ? { lines_added: payload.lines_added } : {},
|
|
@@ -332,7 +354,11 @@ function eventTypeForHook(event) {
|
|
|
332
354
|
if (event.kind === "presence.heartbeat") return "presence.heartbeat";
|
|
333
355
|
if (event.kind === "session.ended") return "presence.stopped";
|
|
334
356
|
if (event.kind === "file.changed") return "workspace.files_changed";
|
|
357
|
+
if (event.kind === "response.completed") return "agent.response_completed";
|
|
335
358
|
if (event.kind === "progress.shared") return "agent.progress_shared";
|
|
359
|
+
if (event.kind === "git.branch_changed" || event.kind === "git.commit_created" || event.kind === "git.push_completed" || event.kind === "git.merge_completed" || event.kind === "git.rebase_completed" || event.kind === "git.stash_created") {
|
|
360
|
+
return event.kind;
|
|
361
|
+
}
|
|
336
362
|
if (event.kind === "shell.completed" && event.outcome && isTestHookEvent(event)) {
|
|
337
363
|
return "workspace.test_completed";
|
|
338
364
|
}
|
|
@@ -373,6 +399,14 @@ function toEnvelope(event, options) {
|
|
|
373
399
|
task_lines_added: typeof event.metadata.task_lines_added === "number" ? event.metadata.task_lines_added : void 0,
|
|
374
400
|
task_lines_deleted: typeof event.metadata.task_lines_deleted === "number" ? event.metadata.task_lines_deleted : void 0,
|
|
375
401
|
task_paths: Array.isArray(event.metadata.task_paths) ? event.metadata.task_paths : void 0
|
|
402
|
+
} : event.kind === "response.completed" ? {
|
|
403
|
+
title: event.metadata.title,
|
|
404
|
+
paths,
|
|
405
|
+
lines_added: event.metadata.lines_added,
|
|
406
|
+
lines_deleted: event.metadata.lines_deleted,
|
|
407
|
+
tool: event.source,
|
|
408
|
+
agent_name: event.source,
|
|
409
|
+
hook_kind: event.kind
|
|
376
410
|
} : event.kind === "presence.heartbeat" ? {
|
|
377
411
|
verification: event.metadata.verification,
|
|
378
412
|
sequence: event.metadata.sequence,
|
|
@@ -396,6 +430,21 @@ function toEnvelope(event, options) {
|
|
|
396
430
|
tool: event.source,
|
|
397
431
|
agent_name: event.source,
|
|
398
432
|
hook_kind: event.kind
|
|
433
|
+
} : event.kind.startsWith("git.") ? {
|
|
434
|
+
title: event.metadata.title,
|
|
435
|
+
previous_branch: event.metadata.previous_branch,
|
|
436
|
+
branch: event.repo?.branch,
|
|
437
|
+
previous_commit: event.metadata.previous_commit,
|
|
438
|
+
commit_sha: event.metadata.commit_sha ?? event.repo?.commit,
|
|
439
|
+
stash_sha: event.metadata.stash_sha,
|
|
440
|
+
paths_known: event.metadata.paths_known,
|
|
441
|
+
paths,
|
|
442
|
+
changes: event.metadata.changes,
|
|
443
|
+
lines_added: event.metadata.lines_added,
|
|
444
|
+
lines_deleted: event.metadata.lines_deleted,
|
|
445
|
+
tool: event.source,
|
|
446
|
+
agent_name: event.source,
|
|
447
|
+
hook_kind: event.kind
|
|
399
448
|
} : {
|
|
400
449
|
paths,
|
|
401
450
|
tool: event.source,
|
|
@@ -416,7 +465,7 @@ function toEnvelope(event, options) {
|
|
|
416
465
|
source: "local_collector",
|
|
417
466
|
type,
|
|
418
467
|
visibility: "project",
|
|
419
|
-
idempotency_key: `${event.source}:${event.id}`,
|
|
468
|
+
idempotency_key: gitIdempotencyKey(event, options) ?? `${event.source}:${event.id}`,
|
|
420
469
|
correlation: {
|
|
421
470
|
session_id: event.sessionId,
|
|
422
471
|
branch: event.repo?.branch,
|
|
@@ -428,6 +477,14 @@ function toEnvelope(event, options) {
|
|
|
428
477
|
payload
|
|
429
478
|
};
|
|
430
479
|
}
|
|
480
|
+
function gitIdempotencyKey(event, options) {
|
|
481
|
+
if (!event.kind.startsWith("git.")) return void 0;
|
|
482
|
+
const identity = event.kind === "git.stash_created" ? event.metadata.stash_sha : event.metadata.commit_sha ?? event.repo?.commit;
|
|
483
|
+
if (typeof identity !== "string" || !identity) return void 0;
|
|
484
|
+
const repository = options.repositoryId ?? "repository";
|
|
485
|
+
const branch = event.repo?.branch ?? "detached";
|
|
486
|
+
return `git:${event.kind.slice(4)}:${repository}:${branch}:${identity}`;
|
|
487
|
+
}
|
|
431
488
|
var SignedBatchClient = class {
|
|
432
489
|
constructor(options) {
|
|
433
490
|
this.options = options;
|
|
@@ -678,13 +735,13 @@ async function updatePresenceSession(source, sessionId, event, cwd = process.cwd
|
|
|
678
735
|
);
|
|
679
736
|
state.linesDeleted = isHeartbeat && state.linesDeleted > MAX_SESSION_LINE_COUNT ? workingDelta : Math.max(state.linesDeleted, workingDelta);
|
|
680
737
|
}
|
|
681
|
-
if (event.kind === "file.changed" && workingLinesAdded !== void 0 && state.taskBaselineLinesAdded !== void 0) {
|
|
738
|
+
if ((event.kind === "file.changed" || event.kind === "lifecycle.after") && workingLinesAdded !== void 0 && state.taskBaselineLinesAdded !== void 0) {
|
|
682
739
|
state.taskLinesAdded = Math.max(
|
|
683
740
|
state.taskLinesAdded,
|
|
684
741
|
Math.max(0, workingLinesAdded - state.taskBaselineLinesAdded)
|
|
685
742
|
);
|
|
686
743
|
}
|
|
687
|
-
if (event.kind === "file.changed" && workingLinesDeleted !== void 0 && state.taskBaselineLinesDeleted !== void 0) {
|
|
744
|
+
if ((event.kind === "file.changed" || event.kind === "lifecycle.after") && workingLinesDeleted !== void 0 && state.taskBaselineLinesDeleted !== void 0) {
|
|
688
745
|
state.taskLinesDeleted = Math.max(
|
|
689
746
|
state.taskLinesDeleted,
|
|
690
747
|
Math.max(0, workingLinesDeleted - state.taskBaselineLinesDeleted)
|
|
@@ -700,6 +757,9 @@ async function updatePresenceSession(source, sessionId, event, cwd = process.cwd
|
|
|
700
757
|
...typeof event.metadata.path === "string" ? [event.metadata.path] : []
|
|
701
758
|
];
|
|
702
759
|
state.paths = [.../* @__PURE__ */ new Set([...state.paths, ...paths])].slice(0, 2e3);
|
|
760
|
+
if (event.kind === "lifecycle.after" && state.taskPaths.length === 0) {
|
|
761
|
+
state.taskPaths = [...new Set(paths)].slice(0, 2e3);
|
|
762
|
+
}
|
|
703
763
|
const category = toolCategory(event);
|
|
704
764
|
if (category) {
|
|
705
765
|
state.toolCounts[category] = (state.toolCounts[category] ?? 0) + 1;
|
|
@@ -708,8 +768,16 @@ async function updatePresenceSession(source, sessionId, event, cwd = process.cwd
|
|
|
708
768
|
if (event.outcome === "success") state.testsPassed += 1;
|
|
709
769
|
if (event.outcome === "failure") state.testsFailed += 1;
|
|
710
770
|
}
|
|
771
|
+
const metrics = metricsFromState(state, measuredAt);
|
|
772
|
+
if (event.kind === "lifecycle.after") {
|
|
773
|
+
state.taskBaselineLinesAdded = workingLinesAdded;
|
|
774
|
+
state.taskBaselineLinesDeleted = workingLinesDeleted;
|
|
775
|
+
state.taskLinesAdded = 0;
|
|
776
|
+
state.taskLinesDeleted = 0;
|
|
777
|
+
state.taskPaths = [];
|
|
778
|
+
}
|
|
711
779
|
await writePresenceState(statePath, state);
|
|
712
|
-
return
|
|
780
|
+
return metrics;
|
|
713
781
|
}
|
|
714
782
|
async function stopPresenceSession(source, sessionId, cwd = process.cwd()) {
|
|
715
783
|
const statePath = presenceStatePath(source, sessionId, cwd);
|
|
@@ -889,16 +957,34 @@ function delay(milliseconds) {
|
|
|
889
957
|
}
|
|
890
958
|
|
|
891
959
|
// src/repo.ts
|
|
960
|
+
import { createHash as createHash2 } from "crypto";
|
|
892
961
|
import { execFile } from "child_process";
|
|
962
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
963
|
+
import { homedir as homedir2 } from "os";
|
|
964
|
+
import { join as join2 } from "path";
|
|
893
965
|
import { promisify } from "util";
|
|
894
966
|
var execFileAsync = promisify(execFile);
|
|
967
|
+
function classifyHeadTransition(reflogAction, headParentCount) {
|
|
968
|
+
const reflog = reflogAction?.toLowerCase() ?? "";
|
|
969
|
+
if (reflog.startsWith("rebase")) return "git.rebase_completed";
|
|
970
|
+
if (reflog.startsWith("merge") && headParentCount && headParentCount > 1) {
|
|
971
|
+
return "git.merge_completed";
|
|
972
|
+
}
|
|
973
|
+
return reflog.startsWith("commit") ? "git.commit_created" : void 0;
|
|
974
|
+
}
|
|
975
|
+
function isObservedPush(previous, current, branchChanged = false) {
|
|
976
|
+
const reflog = current.reflogAction?.toLowerCase() ?? "";
|
|
977
|
+
return Boolean(
|
|
978
|
+
!branchChanged && !reflog.startsWith("pull") && !reflog.startsWith("fetch") && current.commit && current.upstreamCommit === current.commit && previous.upstreamCommit !== current.upstreamCommit
|
|
979
|
+
);
|
|
980
|
+
}
|
|
895
981
|
async function git(cwd, args) {
|
|
896
982
|
try {
|
|
897
983
|
const { stdout } = await execFileAsync("git", args, {
|
|
898
984
|
cwd,
|
|
899
985
|
encoding: "utf8",
|
|
900
986
|
timeout: 2e3,
|
|
901
|
-
maxBuffer:
|
|
987
|
+
maxBuffer: 4 * 1024 * 1024
|
|
902
988
|
});
|
|
903
989
|
return stdout.trim() || void 0;
|
|
904
990
|
} catch {
|
|
@@ -929,10 +1015,24 @@ async function detectDefaultBranch(root) {
|
|
|
929
1015
|
async function detectRepository(cwd = process.cwd()) {
|
|
930
1016
|
const root = await git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
931
1017
|
if (!root) return void 0;
|
|
932
|
-
const [
|
|
1018
|
+
const [
|
|
1019
|
+
remote,
|
|
1020
|
+
branch,
|
|
1021
|
+
commit,
|
|
1022
|
+
upstreamCommit,
|
|
1023
|
+
stashCommit,
|
|
1024
|
+
parents,
|
|
1025
|
+
reflogAction,
|
|
1026
|
+
commitTitle
|
|
1027
|
+
] = await Promise.all([
|
|
933
1028
|
git(root, ["remote", "get-url", "origin"]),
|
|
934
1029
|
git(root, ["branch", "--show-current"]),
|
|
935
|
-
git(root, ["rev-parse", "HEAD"])
|
|
1030
|
+
git(root, ["rev-parse", "HEAD"]),
|
|
1031
|
+
git(root, ["rev-parse", "@{upstream}"]),
|
|
1032
|
+
git(root, ["rev-parse", "refs/stash"]),
|
|
1033
|
+
git(root, ["rev-list", "--parents", "-n", "1", "HEAD"]),
|
|
1034
|
+
git(root, ["reflog", "-1", "--format=%gs"]),
|
|
1035
|
+
git(root, ["log", "-1", "--format=%s"])
|
|
936
1036
|
]);
|
|
937
1037
|
const defaultBranch = await detectDefaultBranch(root);
|
|
938
1038
|
const baseCommit = branch && commit && defaultBranch ? branch === defaultBranch.branch ? commit : await git(root, [
|
|
@@ -953,7 +1053,144 @@ async function detectRepository(cwd = process.cwd()) {
|
|
|
953
1053
|
...commit ? { commit } : {},
|
|
954
1054
|
...defaultBranch ? { defaultBranch: defaultBranch.branch } : {},
|
|
955
1055
|
...baseCommit ? { baseCommit } : {},
|
|
956
|
-
...behindBy !== void 0 ? { behindBy } : {}
|
|
1056
|
+
...behindBy !== void 0 ? { behindBy } : {},
|
|
1057
|
+
...upstreamCommit ? { upstreamCommit } : {},
|
|
1058
|
+
...stashCommit ? { stashCommit } : {},
|
|
1059
|
+
...parents ? { headParentCount: Math.max(0, parents.split(/\s+/).length - 1) } : {},
|
|
1060
|
+
...reflogAction ? { reflogAction } : {},
|
|
1061
|
+
...commitTitle ? { commitTitle } : {}
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
async function observeRepositoryTransitions(source, sessionId, current) {
|
|
1065
|
+
const directory = process.env.WIBE_GIT_STATE_DIR ?? join2(homedir2(), ".wibe", "git-state");
|
|
1066
|
+
const key = createHash2("sha256").update(`${source}\0${sessionId ?? ""}\0${current.root}`).digest("hex");
|
|
1067
|
+
const statePath = join2(directory, `${key}.json`);
|
|
1068
|
+
let previous;
|
|
1069
|
+
try {
|
|
1070
|
+
previous = JSON.parse(await readFile3(statePath, "utf8"));
|
|
1071
|
+
} catch {
|
|
1072
|
+
previous = void 0;
|
|
1073
|
+
}
|
|
1074
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
1075
|
+
await writeFile3(statePath, `${JSON.stringify(current)}
|
|
1076
|
+
`, { mode: 384 });
|
|
1077
|
+
if (!previous) return [];
|
|
1078
|
+
const transitions = [];
|
|
1079
|
+
const branchChanged = Boolean(current.branch) && Boolean(previous.branch) && current.branch !== previous.branch;
|
|
1080
|
+
if (branchChanged) {
|
|
1081
|
+
transitions.push({
|
|
1082
|
+
kind: "git.branch_changed",
|
|
1083
|
+
metadata: {
|
|
1084
|
+
title: "Changed branch",
|
|
1085
|
+
previous_branch: previous.branch,
|
|
1086
|
+
branch: current.branch
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
if (current.stashCommit && current.stashCommit !== previous.stashCommit) {
|
|
1091
|
+
transitions.push({
|
|
1092
|
+
kind: "git.stash_created",
|
|
1093
|
+
metadata: {
|
|
1094
|
+
title: "Stashed changes",
|
|
1095
|
+
stash_sha: current.stashCommit
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
if (!branchChanged && current.commit && previous.commit && current.commit !== previous.commit) {
|
|
1100
|
+
const change = await detectRevisionChanges(
|
|
1101
|
+
current.root,
|
|
1102
|
+
previous.commit,
|
|
1103
|
+
current.commit
|
|
1104
|
+
);
|
|
1105
|
+
const kind = classifyHeadTransition(
|
|
1106
|
+
current.reflogAction,
|
|
1107
|
+
current.headParentCount
|
|
1108
|
+
);
|
|
1109
|
+
if (kind) {
|
|
1110
|
+
transitions.push({
|
|
1111
|
+
kind,
|
|
1112
|
+
metadata: {
|
|
1113
|
+
title: kind === "git.rebase_completed" ? "Rebased" : kind === "git.merge_completed" ? "Merged" : current.commitTitle || "Committed",
|
|
1114
|
+
previous_commit: previous.commit,
|
|
1115
|
+
commit_sha: current.commit,
|
|
1116
|
+
paths_known: change.pathsKnown,
|
|
1117
|
+
paths: change.paths,
|
|
1118
|
+
changes: change.changes,
|
|
1119
|
+
lines_added: change.linesAdded,
|
|
1120
|
+
lines_deleted: change.linesDeleted
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (isObservedPush(previous, current, branchChanged) && current.commit) {
|
|
1126
|
+
const change = previous.upstreamCommit ? await detectRevisionChanges(
|
|
1127
|
+
current.root,
|
|
1128
|
+
previous.upstreamCommit,
|
|
1129
|
+
current.commit
|
|
1130
|
+
) : {
|
|
1131
|
+
pathsKnown: false,
|
|
1132
|
+
paths: [],
|
|
1133
|
+
changes: [],
|
|
1134
|
+
linesAdded: 0,
|
|
1135
|
+
linesDeleted: 0
|
|
1136
|
+
};
|
|
1137
|
+
transitions.push({
|
|
1138
|
+
kind: "git.push_completed",
|
|
1139
|
+
metadata: {
|
|
1140
|
+
title: `Pushed to ${current.branch ?? "remote"}`,
|
|
1141
|
+
commit_sha: current.commit,
|
|
1142
|
+
paths_known: change.pathsKnown,
|
|
1143
|
+
paths: change.paths,
|
|
1144
|
+
changes: change.changes,
|
|
1145
|
+
lines_added: change.linesAdded,
|
|
1146
|
+
lines_deleted: change.linesDeleted
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
return transitions;
|
|
1151
|
+
}
|
|
1152
|
+
async function detectRevisionChanges(root, previous, current) {
|
|
1153
|
+
const [numstat, nameStatus] = await Promise.all([
|
|
1154
|
+
git(root, ["diff", "--numstat", previous, current, "--"]),
|
|
1155
|
+
git(root, ["diff", "--name-status", previous, current, "--"])
|
|
1156
|
+
]);
|
|
1157
|
+
const pathsKnown = numstat !== void 0 && nameStatus !== void 0;
|
|
1158
|
+
const statusByPath = /* @__PURE__ */ new Map();
|
|
1159
|
+
for (const line of nameStatus?.split("\n") ?? []) {
|
|
1160
|
+
if (!line) continue;
|
|
1161
|
+
const [status, ...pathParts] = line.split(" ");
|
|
1162
|
+
const path = pathParts.at(-1);
|
|
1163
|
+
if (!path || isSensitivePath(path)) continue;
|
|
1164
|
+
statusByPath.set(
|
|
1165
|
+
path,
|
|
1166
|
+
status?.startsWith("A") ? "added" : status?.startsWith("D") ? "deleted" : "modified"
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
let linesAdded = 0;
|
|
1170
|
+
let linesDeleted = 0;
|
|
1171
|
+
const changes = [];
|
|
1172
|
+
for (const line of numstat?.split("\n") ?? []) {
|
|
1173
|
+
if (!line) continue;
|
|
1174
|
+
const [added, deleted, ...pathParts] = line.split(" ");
|
|
1175
|
+
const path = pathParts.join(" ");
|
|
1176
|
+
if (!path || isSensitivePath(path)) continue;
|
|
1177
|
+
const additions = /^\d+$/.test(added) ? Number(added) : 0;
|
|
1178
|
+
const deletions = /^\d+$/.test(deleted) ? Number(deleted) : 0;
|
|
1179
|
+
linesAdded += additions;
|
|
1180
|
+
linesDeleted += deletions;
|
|
1181
|
+
changes.push({
|
|
1182
|
+
path,
|
|
1183
|
+
change_type: statusByPath.get(path) ?? "modified",
|
|
1184
|
+
additions,
|
|
1185
|
+
deletions
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
return {
|
|
1189
|
+
pathsKnown,
|
|
1190
|
+
paths: changes.map((change) => change.path).slice(0, 2e3),
|
|
1191
|
+
changes: changes.slice(0, 2e3),
|
|
1192
|
+
linesAdded,
|
|
1193
|
+
linesDeleted
|
|
957
1194
|
};
|
|
958
1195
|
}
|
|
959
1196
|
async function detectWorkingTreeMetrics(root) {
|
|
@@ -1181,7 +1418,10 @@ export {
|
|
|
1181
1418
|
stopPresenceSession,
|
|
1182
1419
|
runPresenceHeartbeat,
|
|
1183
1420
|
presenceStatePath,
|
|
1421
|
+
classifyHeadTransition,
|
|
1422
|
+
isObservedPush,
|
|
1184
1423
|
detectRepository,
|
|
1424
|
+
observeRepositoryTransitions,
|
|
1185
1425
|
detectWorkingTreeMetrics,
|
|
1186
1426
|
sanitizeRemote,
|
|
1187
1427
|
normalizeGitHubRepository,
|
|
@@ -12,12 +12,13 @@ import {
|
|
|
12
12
|
mapCursorHook,
|
|
13
13
|
matchesGitHubRepository,
|
|
14
14
|
normalizeGitHubRepository,
|
|
15
|
+
observeRepositoryTransitions,
|
|
15
16
|
pollDeviceToken,
|
|
16
17
|
requestDeviceAuthorization,
|
|
17
18
|
startPresenceSession,
|
|
18
19
|
stopPresenceSession,
|
|
19
20
|
updatePresenceSession
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-44GDPIWM.js";
|
|
21
22
|
|
|
22
23
|
// src/cli/commands.ts
|
|
23
24
|
import { access, cp, mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -193,7 +194,7 @@ async function emitCommand(adapter, eventName, input) {
|
|
|
193
194
|
const mapper = adapter === "cursor" ? mapCursorHook : adapter === "claude-code" ? mapClaudeCodeHook : mapCodexHook;
|
|
194
195
|
let mappedEvent = mapper(eventName, input);
|
|
195
196
|
const repo = await detectRepository(process.cwd());
|
|
196
|
-
if (repo && (mappedEvent.kind === "session.started" || mappedEvent.kind === "session.ended" || mappedEvent.kind === "lifecycle.before" || mappedEvent.kind === "presence.heartbeat" || mappedEvent.kind === "file.changed")) {
|
|
197
|
+
if (repo && (mappedEvent.kind === "session.started" || mappedEvent.kind === "session.ended" || mappedEvent.kind === "lifecycle.before" || mappedEvent.kind === "lifecycle.after" || mappedEvent.kind === "presence.heartbeat" || mappedEvent.kind === "file.changed")) {
|
|
197
198
|
const workingTree = await detectWorkingTreeMetrics(repo.root);
|
|
198
199
|
if (workingTree) {
|
|
199
200
|
mappedEvent = {
|
|
@@ -257,6 +258,17 @@ async function emitCommand(adapter, eventName, input) {
|
|
|
257
258
|
task_paths: metrics.task_paths
|
|
258
259
|
}
|
|
259
260
|
};
|
|
261
|
+
} else if (mappedEvent.kind === "lifecycle.after" && metrics && metrics.task_paths.length > 0) {
|
|
262
|
+
mappedEvent = {
|
|
263
|
+
...mappedEvent,
|
|
264
|
+
kind: "response.completed",
|
|
265
|
+
metadata: {
|
|
266
|
+
...mappedEvent.metadata,
|
|
267
|
+
paths: metrics.task_paths,
|
|
268
|
+
lines_added: metrics.task_lines_added,
|
|
269
|
+
lines_deleted: metrics.task_lines_deleted
|
|
270
|
+
}
|
|
271
|
+
};
|
|
260
272
|
} else if (mappedEvent.kind === "presence.heartbeat" && metrics) {
|
|
261
273
|
const publicMetadata = { ...mappedEvent.metadata };
|
|
262
274
|
delete publicMetadata.working_tree_paths;
|
|
@@ -274,16 +286,32 @@ async function emitCommand(adapter, eventName, input) {
|
|
|
274
286
|
mappedEvent = { ...mappedEvent, metadata: publicMetadata };
|
|
275
287
|
}
|
|
276
288
|
const event = repo ? { ...mappedEvent, repo } : mappedEvent;
|
|
289
|
+
const repositoryEvents = repo ? (await observeRepositoryTransitions(
|
|
290
|
+
adapter,
|
|
291
|
+
mappedEvent.sessionId,
|
|
292
|
+
repo
|
|
293
|
+
)).map(
|
|
294
|
+
(transition) => createHookEvent({
|
|
295
|
+
source: adapter,
|
|
296
|
+
kind: transition.kind,
|
|
297
|
+
...mappedEvent.sessionId ? { sessionId: mappedEvent.sessionId } : {},
|
|
298
|
+
repo,
|
|
299
|
+
metadata: transition.metadata
|
|
300
|
+
})
|
|
301
|
+
) : [];
|
|
302
|
+
const publishableEvents = [event, ...repositoryEvents].filter(
|
|
303
|
+
(candidate) => eventTypeForHook(candidate)
|
|
304
|
+
);
|
|
277
305
|
const queue = new JsonFileOfflineQueue(queuePath());
|
|
278
306
|
const credential = await loadCredential(process.cwd());
|
|
279
307
|
if (!credential) {
|
|
280
|
-
if (
|
|
308
|
+
if (publishableEvents.length) await queue.enqueue(publishableEvents);
|
|
281
309
|
return {
|
|
282
310
|
exitCode: 0,
|
|
283
|
-
message:
|
|
311
|
+
message: publishableEvents.length ? `Queued ${publishableEvents.length} event(s); this repository is not connected to Wibe.` : `Ignored operational ${event.kind} hook.`
|
|
284
312
|
};
|
|
285
313
|
}
|
|
286
|
-
const
|
|
314
|
+
const client = new SignedBatchClient({
|
|
287
315
|
endpoint: `${credential.appUrl}/api/events/batch`,
|
|
288
316
|
accessToken: credential.accessToken,
|
|
289
317
|
organizationId: credential.organizationId,
|
|
@@ -291,10 +319,19 @@ async function emitCommand(adapter, eventName, input) {
|
|
|
291
319
|
repositoryId: credential.repositoryId,
|
|
292
320
|
deviceId: credential.deviceId,
|
|
293
321
|
queue
|
|
294
|
-
})
|
|
322
|
+
});
|
|
323
|
+
let sent = 0;
|
|
324
|
+
let remaining = await queue.size();
|
|
325
|
+
let deliveryError;
|
|
326
|
+
for (const candidate of publishableEvents) {
|
|
327
|
+
const result = await client.capture(candidate);
|
|
328
|
+
sent += result.sent;
|
|
329
|
+
remaining = result.remaining;
|
|
330
|
+
deliveryError ??= result.error;
|
|
331
|
+
}
|
|
295
332
|
return {
|
|
296
|
-
exitCode:
|
|
297
|
-
message:
|
|
333
|
+
exitCode: deliveryError ? 1 : 0,
|
|
334
|
+
message: deliveryError ? `Queued ${publishableEvents.length} event(s); delivery failed: ${deliveryError}` : publishableEvents.length ? `Delivered ${sent} event(s); ${remaining} queued.` : `Ignored operational ${event.kind} hook.`
|
|
298
335
|
};
|
|
299
336
|
}
|
|
300
337
|
async function shareProgressCommand(options, cwd = process.cwd()) {
|
package/dist/cli.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
setupCommand,
|
|
7
7
|
shareProgressCommand,
|
|
8
8
|
statusCommand
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-G66F67CK.js";
|
|
10
10
|
import {
|
|
11
11
|
runPresenceHeartbeat
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-44GDPIWM.js";
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
15
|
var HELP = `wibe-bridge <command>
|
package/dist/codex-hook.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,14 @@ declare const hookEventKindSchema: z.ZodEnum<{
|
|
|
19
19
|
"shell.completed": "shell.completed";
|
|
20
20
|
"mcp.started": "mcp.started";
|
|
21
21
|
"mcp.completed": "mcp.completed";
|
|
22
|
+
"response.completed": "response.completed";
|
|
22
23
|
"progress.shared": "progress.shared";
|
|
24
|
+
"git.branch_changed": "git.branch_changed";
|
|
25
|
+
"git.commit_created": "git.commit_created";
|
|
26
|
+
"git.push_completed": "git.push_completed";
|
|
27
|
+
"git.merge_completed": "git.merge_completed";
|
|
28
|
+
"git.rebase_completed": "git.rebase_completed";
|
|
29
|
+
"git.stash_created": "git.stash_created";
|
|
23
30
|
unknown: "unknown";
|
|
24
31
|
}>;
|
|
25
32
|
type HookEventKind = z.infer<typeof hookEventKindSchema>;
|
|
@@ -49,7 +56,14 @@ declare const canonicalHookEventSchema: z.ZodObject<{
|
|
|
49
56
|
"shell.completed": "shell.completed";
|
|
50
57
|
"mcp.started": "mcp.started";
|
|
51
58
|
"mcp.completed": "mcp.completed";
|
|
59
|
+
"response.completed": "response.completed";
|
|
52
60
|
"progress.shared": "progress.shared";
|
|
61
|
+
"git.branch_changed": "git.branch_changed";
|
|
62
|
+
"git.commit_created": "git.commit_created";
|
|
63
|
+
"git.push_completed": "git.push_completed";
|
|
64
|
+
"git.merge_completed": "git.merge_completed";
|
|
65
|
+
"git.rebase_completed": "git.rebase_completed";
|
|
66
|
+
"git.stash_created": "git.stash_created";
|
|
53
67
|
unknown: "unknown";
|
|
54
68
|
}>;
|
|
55
69
|
occurredAt: z.ZodString;
|
|
@@ -79,7 +93,7 @@ declare function mapClaudeCodeHook(eventName: string, input: unknown): {
|
|
|
79
93
|
id: string;
|
|
80
94
|
version: 1;
|
|
81
95
|
source: "cursor" | "claude-code" | "codex";
|
|
82
|
-
kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "progress.shared" | "unknown";
|
|
96
|
+
kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "response.completed" | "progress.shared" | "git.branch_changed" | "git.commit_created" | "git.push_completed" | "git.merge_completed" | "git.rebase_completed" | "git.stash_created" | "unknown";
|
|
83
97
|
occurredAt: string;
|
|
84
98
|
metadata: Record<string, SafeValue>;
|
|
85
99
|
sessionId?: string | undefined;
|
|
@@ -100,7 +114,7 @@ declare function mapCodexHook(eventName: string, input: unknown): {
|
|
|
100
114
|
id: string;
|
|
101
115
|
version: 1;
|
|
102
116
|
source: "cursor" | "claude-code" | "codex";
|
|
103
|
-
kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "progress.shared" | "unknown";
|
|
117
|
+
kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "response.completed" | "progress.shared" | "git.branch_changed" | "git.commit_created" | "git.push_completed" | "git.merge_completed" | "git.rebase_completed" | "git.stash_created" | "unknown";
|
|
104
118
|
occurredAt: string;
|
|
105
119
|
metadata: Record<string, SafeValue>;
|
|
106
120
|
sessionId?: string | undefined;
|
|
@@ -121,7 +135,7 @@ declare function mapCursorHook(eventName: string, input: unknown): {
|
|
|
121
135
|
id: string;
|
|
122
136
|
version: 1;
|
|
123
137
|
source: "cursor" | "claude-code" | "codex";
|
|
124
|
-
kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "progress.shared" | "unknown";
|
|
138
|
+
kind: "presence.heartbeat" | "session.started" | "session.ended" | "lifecycle.before" | "lifecycle.after" | "tool.started" | "tool.completed" | "file.changed" | "shell.started" | "shell.completed" | "mcp.started" | "mcp.completed" | "response.completed" | "progress.shared" | "git.branch_changed" | "git.commit_created" | "git.push_completed" | "git.merge_completed" | "git.rebase_completed" | "git.stash_created" | "unknown";
|
|
125
139
|
occurredAt: string;
|
|
126
140
|
metadata: Record<string, SafeValue>;
|
|
127
141
|
sessionId?: string | undefined;
|
|
@@ -251,7 +265,7 @@ interface SignedBatchClientOptions {
|
|
|
251
265
|
batchSize?: number;
|
|
252
266
|
timeoutMs?: number;
|
|
253
267
|
}
|
|
254
|
-
declare function eventTypeForHook(event: CanonicalHookEvent): "presence.heartbeat" | "presence.started" | "presence.stopped" | "workspace.files_changed" | "agent.progress_shared" | "workspace.test_completed" | null;
|
|
268
|
+
declare function eventTypeForHook(event: CanonicalHookEvent): "presence.heartbeat" | "git.branch_changed" | "git.commit_created" | "git.push_completed" | "git.merge_completed" | "git.rebase_completed" | "git.stash_created" | "presence.started" | "presence.stopped" | "workspace.files_changed" | "agent.response_completed" | "agent.progress_shared" | "workspace.test_completed" | null;
|
|
255
269
|
interface FlushResult {
|
|
256
270
|
sent: number;
|
|
257
271
|
remaining: number;
|
|
@@ -307,16 +321,28 @@ interface RepositoryInfo {
|
|
|
307
321
|
defaultBranch?: string;
|
|
308
322
|
baseCommit?: string;
|
|
309
323
|
behindBy?: number;
|
|
324
|
+
upstreamCommit?: string;
|
|
325
|
+
stashCommit?: string;
|
|
326
|
+
headParentCount?: number;
|
|
327
|
+
reflogAction?: string;
|
|
328
|
+
commitTitle?: string;
|
|
310
329
|
}
|
|
311
330
|
interface WorkingTreeMetrics {
|
|
312
331
|
linesAdded: number;
|
|
313
332
|
linesDeleted: number;
|
|
314
333
|
paths: string[];
|
|
315
334
|
}
|
|
335
|
+
interface RepositoryTransition {
|
|
336
|
+
kind: Extract<HookEventKind, "git.branch_changed" | "git.commit_created" | "git.push_completed" | "git.merge_completed" | "git.rebase_completed" | "git.stash_created">;
|
|
337
|
+
metadata: Record<string, SafeValue>;
|
|
338
|
+
}
|
|
339
|
+
declare function classifyHeadTransition(reflogAction: string | undefined, headParentCount: number | undefined): RepositoryTransition["kind"] | undefined;
|
|
340
|
+
declare function isObservedPush(previous: RepositoryInfo, current: RepositoryInfo, branchChanged?: boolean): boolean;
|
|
316
341
|
declare function detectRepository(cwd?: string): Promise<RepositoryInfo | undefined>;
|
|
342
|
+
declare function observeRepositoryTransitions(source: AgentSource, sessionId: string | undefined, current: RepositoryInfo): Promise<RepositoryTransition[]>;
|
|
317
343
|
declare function detectWorkingTreeMetrics(root: string): Promise<WorkingTreeMetrics | undefined>;
|
|
318
344
|
declare function sanitizeRemote(remote: string): string;
|
|
319
345
|
declare function normalizeGitHubRepository(value: string): string | undefined;
|
|
320
346
|
declare function matchesGitHubRepository(remote: string | undefined, expected: string): boolean;
|
|
321
347
|
|
|
322
|
-
export { type AgentSource, type CanonicalHookEvent, type CredentialStore, type DeviceAuthorization, type DeviceTokenResponse, type FlushResult, type HookEventKind, JsonFileOfflineQueue, MemoryOfflineQueue, type OfflineQueue, PRESENCE_HEARTBEAT_INTERVAL_MS, type PresenceMetrics, type RedactionOptions, type RepositoryInfo, type SafeValue, SignedBatchClient, type SignedBatchClientOptions, type StoredCredential, SystemCredentialStore, type WorkingTreeMetrics, agentSourceSchema, canonicalHookEventSchema, createHookEvent, detectRepository, detectWorkingTreeMetrics, deviceAuthorizationSchema, deviceTokenResponseSchema, eventTypeForHook, hookEventKindSchema, mapClaudeCodeHook, mapCodexHook, mapCursorHook, matchesGitHubRepository, normalizeGitHubRepository, pollDeviceToken, presenceStatePath, redact, requestDeviceAuthorization, runPresenceHeartbeat, safeMetadata, safeValueSchema, sanitizeRemote, startPresenceSession, stopPresenceSession, updatePresenceSession };
|
|
348
|
+
export { type AgentSource, type CanonicalHookEvent, type CredentialStore, type DeviceAuthorization, type DeviceTokenResponse, type FlushResult, type HookEventKind, JsonFileOfflineQueue, MemoryOfflineQueue, type OfflineQueue, PRESENCE_HEARTBEAT_INTERVAL_MS, type PresenceMetrics, type RedactionOptions, type RepositoryInfo, type RepositoryTransition, type SafeValue, SignedBatchClient, type SignedBatchClientOptions, type StoredCredential, SystemCredentialStore, type WorkingTreeMetrics, agentSourceSchema, canonicalHookEventSchema, classifyHeadTransition, createHookEvent, detectRepository, detectWorkingTreeMetrics, deviceAuthorizationSchema, deviceTokenResponseSchema, eventTypeForHook, hookEventKindSchema, isObservedPush, mapClaudeCodeHook, mapCodexHook, mapCursorHook, matchesGitHubRepository, normalizeGitHubRepository, observeRepositoryTransitions, pollDeviceToken, presenceStatePath, redact, requestDeviceAuthorization, runPresenceHeartbeat, safeMetadata, safeValueSchema, sanitizeRemote, startPresenceSession, stopPresenceSession, updatePresenceSession };
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SystemCredentialStore,
|
|
7
7
|
agentSourceSchema,
|
|
8
8
|
canonicalHookEventSchema,
|
|
9
|
+
classifyHeadTransition,
|
|
9
10
|
createHookEvent,
|
|
10
11
|
detectRepository,
|
|
11
12
|
detectWorkingTreeMetrics,
|
|
@@ -13,11 +14,13 @@ import {
|
|
|
13
14
|
deviceTokenResponseSchema,
|
|
14
15
|
eventTypeForHook,
|
|
15
16
|
hookEventKindSchema,
|
|
17
|
+
isObservedPush,
|
|
16
18
|
mapClaudeCodeHook,
|
|
17
19
|
mapCodexHook,
|
|
18
20
|
mapCursorHook,
|
|
19
21
|
matchesGitHubRepository,
|
|
20
22
|
normalizeGitHubRepository,
|
|
23
|
+
observeRepositoryTransitions,
|
|
21
24
|
pollDeviceToken,
|
|
22
25
|
presenceStatePath,
|
|
23
26
|
redact,
|
|
@@ -29,7 +32,7 @@ import {
|
|
|
29
32
|
startPresenceSession,
|
|
30
33
|
stopPresenceSession,
|
|
31
34
|
updatePresenceSession
|
|
32
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-44GDPIWM.js";
|
|
33
36
|
export {
|
|
34
37
|
JsonFileOfflineQueue,
|
|
35
38
|
MemoryOfflineQueue,
|
|
@@ -38,6 +41,7 @@ export {
|
|
|
38
41
|
SystemCredentialStore,
|
|
39
42
|
agentSourceSchema,
|
|
40
43
|
canonicalHookEventSchema,
|
|
44
|
+
classifyHeadTransition,
|
|
41
45
|
createHookEvent,
|
|
42
46
|
detectRepository,
|
|
43
47
|
detectWorkingTreeMetrics,
|
|
@@ -45,11 +49,13 @@ export {
|
|
|
45
49
|
deviceTokenResponseSchema,
|
|
46
50
|
eventTypeForHook,
|
|
47
51
|
hookEventKindSchema,
|
|
52
|
+
isObservedPush,
|
|
48
53
|
mapClaudeCodeHook,
|
|
49
54
|
mapCodexHook,
|
|
50
55
|
mapCursorHook,
|
|
51
56
|
matchesGitHubRepository,
|
|
52
57
|
normalizeGitHubRepository,
|
|
58
|
+
observeRepositoryTransitions,
|
|
53
59
|
pollDeviceToken,
|
|
54
60
|
presenceStatePath,
|
|
55
61
|
redact,
|