harnery 0.31.5 → 0.31.6
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/dist/core/agents/coord-client.d.ts +4 -1
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +22 -5
- package/dist/core/hooks/adapter/parse.d.ts +1 -1
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +22 -5
- package/package.json +1 -1
- package/src/core/agents/coord-client.ts +28 -4
- package/src/core/hooks/adapter/parse.ts +24 -5
|
@@ -162,7 +162,10 @@ export declare function resolveOwnerByPidmap(root: string): {
|
|
|
162
162
|
* `instance_id`, or null if there's no session-id env var or no live heartbeat
|
|
163
163
|
* carries it. "Live" reuses the same 10-minute freshness window the singleton
|
|
164
164
|
* fallback applies, so a stale heartbeat from a prior session of the same id
|
|
165
|
-
* doesn't resolve.
|
|
165
|
+
* doesn't resolve. When legacy Cursor Glass state contains both the canonical
|
|
166
|
+
* bare id and its raw `bc-` alias, candidate order wins first; ties prefer a
|
|
167
|
+
* named heartbeat, then the newest heartbeat, with instance id as a stable
|
|
168
|
+
* final tie-breaker. Filesystem enumeration order never decides identity.
|
|
166
169
|
*
|
|
167
170
|
* Exported for unit testing with an injectable root.
|
|
168
171
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yIAAyI;IACzI,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CA+B7E;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;AAeD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtD;AAiED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2CxD;AAgCD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,GAAG,MAAM,CAAC;CAC5F,CAwCA;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAClD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,QAAQ,GAAG,iBAAiB,GAAG,MAAM,CAAC;CAC/C,CAyBA;AA0DD
|
|
1
|
+
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -482,7 +482,10 @@ function shouldPreferSessionEnv() {
|
|
|
482
482
|
* `instance_id`, or null if there's no session-id env var or no live heartbeat
|
|
483
483
|
* carries it. "Live" reuses the same 10-minute freshness window the singleton
|
|
484
484
|
* fallback applies, so a stale heartbeat from a prior session of the same id
|
|
485
|
-
* doesn't resolve.
|
|
485
|
+
* doesn't resolve. When legacy Cursor Glass state contains both the canonical
|
|
486
|
+
* bare id and its raw `bc-` alias, candidate order wins first; ties prefer a
|
|
487
|
+
* named heartbeat, then the newest heartbeat, with instance id as a stable
|
|
488
|
+
* final tie-breaker. Filesystem enumeration order never decides identity.
|
|
486
489
|
*
|
|
487
490
|
* Exported for unit testing with an injectable root.
|
|
488
491
|
*/
|
|
@@ -502,24 +505,38 @@ export function resolveOwnerBySessionEnv(root) {
|
|
|
502
505
|
catch {
|
|
503
506
|
return null;
|
|
504
507
|
}
|
|
508
|
+
const matches = [];
|
|
505
509
|
for (const file of files) {
|
|
506
510
|
if (!file.endsWith(".json"))
|
|
507
511
|
continue;
|
|
508
512
|
try {
|
|
509
513
|
const parsed = JSON.parse(readFileSync(resolve(activeDir, file), "utf8"));
|
|
510
|
-
if (!parsed
|
|
514
|
+
if (!parsed)
|
|
515
|
+
continue;
|
|
516
|
+
const sessionPreference = sessionIds.indexOf(parsed.session_id);
|
|
517
|
+
if (sessionPreference === -1)
|
|
511
518
|
continue;
|
|
512
519
|
if (typeof parsed.instance_id !== "string")
|
|
513
520
|
continue;
|
|
514
521
|
const ts = Date.parse(parsed.last_heartbeat);
|
|
515
|
-
if (Number.isFinite(ts)
|
|
516
|
-
|
|
522
|
+
if (!Number.isFinite(ts) || ts < cutoffMs)
|
|
523
|
+
continue;
|
|
524
|
+
matches.push({
|
|
525
|
+
instanceId: parsed.instance_id,
|
|
526
|
+
sessionPreference,
|
|
527
|
+
hasName: typeof parsed.name === "string" && parsed.name.trim().length > 0,
|
|
528
|
+
lastHeartbeatMs: ts,
|
|
529
|
+
});
|
|
517
530
|
}
|
|
518
531
|
catch {
|
|
519
532
|
// skip malformed
|
|
520
533
|
}
|
|
521
534
|
}
|
|
522
|
-
|
|
535
|
+
matches.sort((a, b) => a.sessionPreference - b.sessionPreference ||
|
|
536
|
+
Number(b.hasName) - Number(a.hasName) ||
|
|
537
|
+
b.lastHeartbeatMs - a.lastHeartbeatMs ||
|
|
538
|
+
a.instanceId.localeCompare(b.instanceId));
|
|
539
|
+
return matches[0]?.instanceId ?? null;
|
|
523
540
|
}
|
|
524
541
|
/**
|
|
525
542
|
* Return the instance_id of the sole live agent in this coord root, or null
|
|
@@ -40,7 +40,7 @@ export interface ParsedPayload {
|
|
|
40
40
|
* Parse the raw stdin payload string for any adapter. Returns null when JSON
|
|
41
41
|
* parse fails (Cursor occasionally fires hooks with no payload).
|
|
42
42
|
*/
|
|
43
|
-
export declare function parsePayload(raw: string,
|
|
43
|
+
export declare function parsePayload(raw: string, adapter: Adapter): ParsedPayload | null;
|
|
44
44
|
/**
|
|
45
45
|
* Pull the bash command string out of a Bash/Shell tool_input. Returns
|
|
46
46
|
* undefined for non-shell tools.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CA+ChF;AAQD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,OAAO,GACjB,MAAM,GAAG,SAAS,CASpB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO7E;AAmBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAChB;IAAE,UAAU,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAiCjE;AAED,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,aAAa,GACb,oBAAoB,GACpB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,cAAc,GACd,eAAe,GACf,uBAAuB,GACvB,4BAA4B,GAC5B,8BAA8B,CAAC"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Parse the raw stdin payload string for any adapter. Returns null when JSON
|
|
14
14
|
* parse fails (Cursor occasionally fires hooks with no payload).
|
|
15
15
|
*/
|
|
16
|
-
export function parsePayload(raw,
|
|
16
|
+
export function parsePayload(raw, adapter) {
|
|
17
17
|
if (!raw || raw.trim().length === 0)
|
|
18
18
|
return null;
|
|
19
19
|
let json;
|
|
@@ -23,13 +23,24 @@ export function parsePayload(raw, _adapter) {
|
|
|
23
23
|
catch {
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
+
const sessionId = normalizeSessionId(adapter, pickStr(json, "session_id"));
|
|
27
|
+
const conversationId = normalizeSessionId(adapter, pickStr(json, "conversation_id"));
|
|
28
|
+
const parentSessionId = normalizeSessionId(adapter, pickStr(json, "parent_session_id"));
|
|
29
|
+
const normalizedRaw = adapter === "cursor"
|
|
30
|
+
? {
|
|
31
|
+
...json,
|
|
32
|
+
...(sessionId ? { session_id: sessionId } : {}),
|
|
33
|
+
...(conversationId ? { conversation_id: conversationId } : {}),
|
|
34
|
+
...(parentSessionId ? { parent_session_id: parentSessionId } : {}),
|
|
35
|
+
}
|
|
36
|
+
: json;
|
|
26
37
|
return {
|
|
27
38
|
hook_event_name: pickStr(json, "hook_event_name"),
|
|
28
|
-
session_id:
|
|
39
|
+
session_id: sessionId,
|
|
29
40
|
agent_id: pickStr(json, "agent_id"),
|
|
30
41
|
subagent_id: pickStr(json, "subagent_id"),
|
|
31
|
-
conversation_id:
|
|
32
|
-
parent_session_id:
|
|
42
|
+
conversation_id: conversationId,
|
|
43
|
+
parent_session_id: parentSessionId,
|
|
33
44
|
turn_id: pickStr(json, "turn_id"),
|
|
34
45
|
parent_turn_id: pickStr(json, "parent_turn_id"),
|
|
35
46
|
transcript_path: pickStr(json, "transcript_path"),
|
|
@@ -46,9 +57,15 @@ export function parsePayload(raw, _adapter) {
|
|
|
46
57
|
clean_exit: pickBool(json, "clean_exit"),
|
|
47
58
|
exit_status: pickStr(json, "exit_status"),
|
|
48
59
|
reason: pickStr(json, "reason"),
|
|
49
|
-
raw:
|
|
60
|
+
raw: normalizedRaw,
|
|
50
61
|
};
|
|
51
62
|
}
|
|
63
|
+
/** Cursor Glass prefixes conversation ids with `bc-`; coordination uses the bare id everywhere. */
|
|
64
|
+
function normalizeSessionId(adapter, value) {
|
|
65
|
+
if (adapter !== "cursor" || !value?.startsWith("bc-") || value.length <= 3)
|
|
66
|
+
return value;
|
|
67
|
+
return value.slice(3);
|
|
68
|
+
}
|
|
52
69
|
/**
|
|
53
70
|
* Pull the bash command string out of a Bash/Shell tool_input. Returns
|
|
54
71
|
* undefined for non-shell tools.
|
package/package.json
CHANGED
|
@@ -528,7 +528,10 @@ function shouldPreferSessionEnv(): boolean {
|
|
|
528
528
|
* `instance_id`, or null if there's no session-id env var or no live heartbeat
|
|
529
529
|
* carries it. "Live" reuses the same 10-minute freshness window the singleton
|
|
530
530
|
* fallback applies, so a stale heartbeat from a prior session of the same id
|
|
531
|
-
* doesn't resolve.
|
|
531
|
+
* doesn't resolve. When legacy Cursor Glass state contains both the canonical
|
|
532
|
+
* bare id and its raw `bc-` alias, candidate order wins first; ties prefer a
|
|
533
|
+
* named heartbeat, then the newest heartbeat, with instance id as a stable
|
|
534
|
+
* final tie-breaker. Filesystem enumeration order never decides identity.
|
|
532
535
|
*
|
|
533
536
|
* Exported for unit testing with an injectable root.
|
|
534
537
|
*/
|
|
@@ -546,19 +549,40 @@ export function resolveOwnerBySessionEnv(root: string): string | null {
|
|
|
546
549
|
} catch {
|
|
547
550
|
return null;
|
|
548
551
|
}
|
|
552
|
+
const matches: Array<{
|
|
553
|
+
instanceId: string;
|
|
554
|
+
sessionPreference: number;
|
|
555
|
+
hasName: boolean;
|
|
556
|
+
lastHeartbeatMs: number;
|
|
557
|
+
}> = [];
|
|
549
558
|
for (const file of files) {
|
|
550
559
|
if (!file.endsWith(".json")) continue;
|
|
551
560
|
try {
|
|
552
561
|
const parsed = JSON.parse(readFileSync(resolve(activeDir, file), "utf8"));
|
|
553
|
-
if (!parsed
|
|
562
|
+
if (!parsed) continue;
|
|
563
|
+
const sessionPreference = sessionIds.indexOf(parsed.session_id);
|
|
564
|
+
if (sessionPreference === -1) continue;
|
|
554
565
|
if (typeof parsed.instance_id !== "string") continue;
|
|
555
566
|
const ts = Date.parse(parsed.last_heartbeat);
|
|
556
|
-
if (Number.isFinite(ts)
|
|
567
|
+
if (!Number.isFinite(ts) || ts < cutoffMs) continue;
|
|
568
|
+
matches.push({
|
|
569
|
+
instanceId: parsed.instance_id,
|
|
570
|
+
sessionPreference,
|
|
571
|
+
hasName: typeof parsed.name === "string" && parsed.name.trim().length > 0,
|
|
572
|
+
lastHeartbeatMs: ts,
|
|
573
|
+
});
|
|
557
574
|
} catch {
|
|
558
575
|
// skip malformed
|
|
559
576
|
}
|
|
560
577
|
}
|
|
561
|
-
|
|
578
|
+
matches.sort(
|
|
579
|
+
(a, b) =>
|
|
580
|
+
a.sessionPreference - b.sessionPreference ||
|
|
581
|
+
Number(b.hasName) - Number(a.hasName) ||
|
|
582
|
+
b.lastHeartbeatMs - a.lastHeartbeatMs ||
|
|
583
|
+
a.instanceId.localeCompare(b.instanceId),
|
|
584
|
+
);
|
|
585
|
+
return matches[0]?.instanceId ?? null;
|
|
562
586
|
}
|
|
563
587
|
|
|
564
588
|
/**
|
|
@@ -43,7 +43,7 @@ export interface ParsedPayload {
|
|
|
43
43
|
* Parse the raw stdin payload string for any adapter. Returns null when JSON
|
|
44
44
|
* parse fails (Cursor occasionally fires hooks with no payload).
|
|
45
45
|
*/
|
|
46
|
-
export function parsePayload(raw: string,
|
|
46
|
+
export function parsePayload(raw: string, adapter: Adapter): ParsedPayload | null {
|
|
47
47
|
if (!raw || raw.trim().length === 0) return null;
|
|
48
48
|
let json: Record<string, unknown>;
|
|
49
49
|
try {
|
|
@@ -52,13 +52,26 @@ export function parsePayload(raw: string, _adapter: Adapter): ParsedPayload | nu
|
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const sessionId = normalizeSessionId(adapter, pickStr(json, "session_id"));
|
|
56
|
+
const conversationId = normalizeSessionId(adapter, pickStr(json, "conversation_id"));
|
|
57
|
+
const parentSessionId = normalizeSessionId(adapter, pickStr(json, "parent_session_id"));
|
|
58
|
+
const normalizedRaw =
|
|
59
|
+
adapter === "cursor"
|
|
60
|
+
? {
|
|
61
|
+
...json,
|
|
62
|
+
...(sessionId ? { session_id: sessionId } : {}),
|
|
63
|
+
...(conversationId ? { conversation_id: conversationId } : {}),
|
|
64
|
+
...(parentSessionId ? { parent_session_id: parentSessionId } : {}),
|
|
65
|
+
}
|
|
66
|
+
: json;
|
|
67
|
+
|
|
55
68
|
return {
|
|
56
69
|
hook_event_name: pickStr(json, "hook_event_name"),
|
|
57
|
-
session_id:
|
|
70
|
+
session_id: sessionId,
|
|
58
71
|
agent_id: pickStr(json, "agent_id"),
|
|
59
72
|
subagent_id: pickStr(json, "subagent_id"),
|
|
60
|
-
conversation_id:
|
|
61
|
-
parent_session_id:
|
|
73
|
+
conversation_id: conversationId,
|
|
74
|
+
parent_session_id: parentSessionId,
|
|
62
75
|
turn_id: pickStr(json, "turn_id"),
|
|
63
76
|
parent_turn_id: pickStr(json, "parent_turn_id"),
|
|
64
77
|
transcript_path: pickStr(json, "transcript_path"),
|
|
@@ -75,10 +88,16 @@ export function parsePayload(raw: string, _adapter: Adapter): ParsedPayload | nu
|
|
|
75
88
|
clean_exit: pickBool(json, "clean_exit"),
|
|
76
89
|
exit_status: pickStr(json, "exit_status"),
|
|
77
90
|
reason: pickStr(json, "reason"),
|
|
78
|
-
raw:
|
|
91
|
+
raw: normalizedRaw,
|
|
79
92
|
};
|
|
80
93
|
}
|
|
81
94
|
|
|
95
|
+
/** Cursor Glass prefixes conversation ids with `bc-`; coordination uses the bare id everywhere. */
|
|
96
|
+
function normalizeSessionId(adapter: Adapter, value: string | undefined): string | undefined {
|
|
97
|
+
if (adapter !== "cursor" || !value?.startsWith("bc-") || value.length <= 3) return value;
|
|
98
|
+
return value.slice(3);
|
|
99
|
+
}
|
|
100
|
+
|
|
82
101
|
/**
|
|
83
102
|
* Pull the bash command string out of a Bash/Shell tool_input. Returns
|
|
84
103
|
* undefined for non-shell tools.
|