nemo-relay-openclaw 0.3.0 → 0.4.0
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/src/hook-replay/llm.js +64 -31
- package/dist/src/hook-replay/marks.d.ts +1 -0
- package/dist/src/hook-replay/marks.js +1 -1
- package/dist/src/hook-replay/session.d.ts +27 -9
- package/dist/src/hook-replay/session.js +146 -46
- package/dist/src/hook-replay/tool.js +18 -3
- package/dist/src/hooks-backend.d.ts +27 -3
- package/dist/src/hooks-backend.js +230 -24
- package/package.json +2 -2
- package/src/hook-replay/llm.ts +70 -31
- package/src/hook-replay/marks.ts +2 -1
- package/src/hook-replay/session.ts +178 -53
- package/src/hook-replay/tool.ts +18 -3
- package/src/hooks-backend.ts +283 -21
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { emitMark, toJsonRecord, toJsonValue } from './marks.js';
|
|
4
|
-
import { evictExpiredCorrelationRecords, ensureSession, insertBoundedRecord,
|
|
4
|
+
import { evictExpiredCorrelationRecords, ensureSession, insertBoundedRecord, resolveSessionOwnerKey, } from './session.js';
|
|
5
5
|
import { llmKey, modelTimingKey, modelTimingLlmKey, nowMicros, startMicrosFromDuration } from './correlation.js';
|
|
6
6
|
/**
|
|
7
7
|
* Store one OpenClaw llm_input snapshot and replay it immediately if the matching
|
|
@@ -9,12 +9,14 @@ import { llmKey, modelTimingKey, modelTimingLlmKey, nowMicros, startMicrosFromDu
|
|
|
9
9
|
*/
|
|
10
10
|
export function recordLlmInput(manager, event, ctx) {
|
|
11
11
|
evictExpiredReplayRecords(manager);
|
|
12
|
+
const observedAtMicros = nowMicros();
|
|
12
13
|
const session = ensureSession(manager, {
|
|
13
14
|
sessionId: event.sessionId,
|
|
14
15
|
sessionKey: ctx.sessionKey,
|
|
15
16
|
runId: event.runId,
|
|
16
17
|
agentId: ctx.agentId,
|
|
17
18
|
source: 'lazy_session',
|
|
19
|
+
timestamp: observedAtMicros,
|
|
18
20
|
});
|
|
19
21
|
if (!session) {
|
|
20
22
|
return;
|
|
@@ -26,7 +28,7 @@ export function recordLlmInput(manager, event, ctx) {
|
|
|
26
28
|
const key = llmKey(event);
|
|
27
29
|
const input = createInputRecord(session, event);
|
|
28
30
|
insertBoundedRecord(manager.state.llmInputs, key, input, manager.config.correlation.maxRecordsPerKey);
|
|
29
|
-
const pending = shiftOldest(manager.state.llmOutputsPendingInput, key, (record) => record.
|
|
31
|
+
const pending = shiftOldest(manager.state.llmOutputsPendingInput, key, (record) => record.sessionOwnerKey === session.ownerKey);
|
|
30
32
|
if (!pending) {
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
@@ -48,22 +50,24 @@ export function recordLlmInput(manager, event, ctx) {
|
|
|
48
50
|
*/
|
|
49
51
|
export function recordLlmOutput(manager, event, ctx) {
|
|
50
52
|
evictExpiredReplayRecords(manager);
|
|
53
|
+
const observedAtMicros = nowMicros();
|
|
51
54
|
const session = ensureSession(manager, {
|
|
52
55
|
sessionId: event.sessionId,
|
|
53
56
|
sessionKey: ctx.sessionKey,
|
|
54
57
|
runId: event.runId,
|
|
55
58
|
agentId: ctx.agentId,
|
|
56
59
|
source: 'lazy_session',
|
|
60
|
+
timestamp: observedAtMicros,
|
|
57
61
|
});
|
|
58
62
|
if (!session) {
|
|
59
63
|
return;
|
|
60
64
|
}
|
|
61
65
|
const key = llmKey(event);
|
|
62
66
|
if (hasTrajectoryReplay(session, event.runId)) {
|
|
63
|
-
shiftOldest(manager.state.llmInputs, key, (record) => record.
|
|
67
|
+
shiftOldest(manager.state.llmInputs, key, (record) => record.sessionOwnerKey === session.ownerKey);
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
|
-
const input = shiftOldest(manager.state.llmInputs, key, (record) => record.
|
|
70
|
+
const input = shiftOldest(manager.state.llmInputs, key, (record) => record.sessionOwnerKey === session.ownerKey);
|
|
67
71
|
if (input) {
|
|
68
72
|
replayLlmOutput({
|
|
69
73
|
manager,
|
|
@@ -75,7 +79,7 @@ export function recordLlmOutput(manager, event, ctx) {
|
|
|
75
79
|
return;
|
|
76
80
|
}
|
|
77
81
|
const pending = {
|
|
78
|
-
|
|
82
|
+
sessionOwnerKey: session.ownerKey,
|
|
79
83
|
sessionId: event.sessionId,
|
|
80
84
|
runId: event.runId,
|
|
81
85
|
provider: event.provider,
|
|
@@ -120,7 +124,7 @@ export function recordBeforeMessageWrite(manager, event, ctx) {
|
|
|
120
124
|
if (provider && model && (assistantTexts.length > 0 || assistantToolCalls.length > 0 || usage !== undefined)) {
|
|
121
125
|
session.assistantMessageWrites ??= [];
|
|
122
126
|
session.assistantMessageWrites.push({
|
|
123
|
-
|
|
127
|
+
sessionOwnerKey: session.ownerKey,
|
|
124
128
|
provider,
|
|
125
129
|
model,
|
|
126
130
|
assistantTexts,
|
|
@@ -158,7 +162,7 @@ export function recordModelCallStarted(manager, event, ctx) {
|
|
|
158
162
|
return;
|
|
159
163
|
}
|
|
160
164
|
insertBoundedRecord(manager.state.modelCallsByCallId, modelTimingKey(event), {
|
|
161
|
-
|
|
165
|
+
sessionOwnerKey: session.ownerKey,
|
|
162
166
|
sessionId: session.sessionId,
|
|
163
167
|
runId: event.runId,
|
|
164
168
|
callId: event.callId,
|
|
@@ -191,7 +195,7 @@ export function recordModelCallEnded(manager, event, ctx) {
|
|
|
191
195
|
const existing = latestUnendedRecord(manager.state.modelCallsByCallId.get(byCallKey), session);
|
|
192
196
|
const record = existing ??
|
|
193
197
|
{
|
|
194
|
-
|
|
198
|
+
sessionOwnerKey: session.ownerKey,
|
|
195
199
|
sessionId: session.sessionId,
|
|
196
200
|
runId: event.runId,
|
|
197
201
|
callId: event.callId,
|
|
@@ -205,7 +209,7 @@ export function recordModelCallEnded(manager, event, ctx) {
|
|
|
205
209
|
insertBoundedRecord(manager.state.modelCallsByCallId, byCallKey, record, manager.config.correlation.maxRecordsPerKey);
|
|
206
210
|
}
|
|
207
211
|
insertBoundedRecord(manager.state.modelTimingsByLlmKey, modelTimingLlmKey({
|
|
208
|
-
sessionId: session.
|
|
212
|
+
sessionId: session.ownerKey,
|
|
209
213
|
runId: event.runId,
|
|
210
214
|
provider: event.provider,
|
|
211
215
|
model: event.model,
|
|
@@ -219,7 +223,7 @@ export function replayPendingLlmOutputsForSession(manager, session, options) {
|
|
|
219
223
|
for (const [key, records] of [...manager.state.llmOutputsPendingInput]) {
|
|
220
224
|
const remaining = [];
|
|
221
225
|
for (const record of records) {
|
|
222
|
-
if (record.
|
|
226
|
+
if (record.sessionOwnerKey !== session.ownerKey) {
|
|
223
227
|
remaining.push(record);
|
|
224
228
|
continue;
|
|
225
229
|
}
|
|
@@ -274,7 +278,7 @@ export function replayAgentEndMessages(manager, event, ctx, session) {
|
|
|
274
278
|
export function emitUnpairedModelCallTimingMarks(manager, session) {
|
|
275
279
|
for (const records of manager.state.modelCallsByCallId.values()) {
|
|
276
280
|
for (const record of records) {
|
|
277
|
-
if (record.
|
|
281
|
+
if (record.sessionOwnerKey !== session.ownerKey || record.consumed || record.endedAtMs !== undefined) {
|
|
278
282
|
continue;
|
|
279
283
|
}
|
|
280
284
|
emitModelTimingMark(manager, session, 'openclaw.model_call_timing_unpaired', record);
|
|
@@ -284,7 +288,7 @@ export function emitUnpairedModelCallTimingMarks(manager, session) {
|
|
|
284
288
|
const unpairedEnded = [];
|
|
285
289
|
for (const records of manager.state.modelTimingsByLlmKey.values()) {
|
|
286
290
|
for (const record of records) {
|
|
287
|
-
if (record.
|
|
291
|
+
if (record.sessionOwnerKey !== session.ownerKey || record.consumed) {
|
|
288
292
|
continue;
|
|
289
293
|
}
|
|
290
294
|
unpairedEnded.push(record);
|
|
@@ -362,7 +366,7 @@ function replayExpiredPendingOutput(manager, key, record) {
|
|
|
362
366
|
if (!removeRecord(manager.state.llmOutputsPendingInput, key, record)) {
|
|
363
367
|
return;
|
|
364
368
|
}
|
|
365
|
-
const session = manager.state.sessions.get(record.
|
|
369
|
+
const session = manager.state.sessions.get(record.sessionOwnerKey);
|
|
366
370
|
if (!session) {
|
|
367
371
|
manager.state.counters.skippedEvents += 1;
|
|
368
372
|
return;
|
|
@@ -408,7 +412,9 @@ function replayLlmOutput(params) {
|
|
|
408
412
|
const metadata = toJsonRecord({
|
|
409
413
|
source,
|
|
410
414
|
runId: event.runId,
|
|
411
|
-
sessionId:
|
|
415
|
+
sessionId: session.sessionId,
|
|
416
|
+
sessionKey: session.sessionKey,
|
|
417
|
+
agentId: session.agentId,
|
|
412
418
|
provider: event.provider,
|
|
413
419
|
model: event.model,
|
|
414
420
|
callId: timing?.callId,
|
|
@@ -452,7 +458,7 @@ function replayAssistantMessageWrites(manager, session, event, ctx) {
|
|
|
452
458
|
},
|
|
453
459
|
ctx,
|
|
454
460
|
input: {
|
|
455
|
-
|
|
461
|
+
sessionOwnerKey: session.ownerKey,
|
|
456
462
|
sessionId: session.sessionId,
|
|
457
463
|
runId,
|
|
458
464
|
provider: record.provider,
|
|
@@ -475,13 +481,13 @@ function replayAssistantMessageWrites(manager, session, event, ctx) {
|
|
|
475
481
|
/** Consume the next unpaired timing record for message-write trajectory replay. */
|
|
476
482
|
function consumeNextTimingCandidate(manager, session, input) {
|
|
477
483
|
const key = modelTimingLlmKey({
|
|
478
|
-
sessionId: session.
|
|
484
|
+
sessionId: session.ownerKey,
|
|
479
485
|
runId: input.runId,
|
|
480
486
|
provider: input.provider,
|
|
481
487
|
model: input.model,
|
|
482
488
|
});
|
|
483
489
|
const records = manager.state.modelTimingsByLlmKey.get(key) ?? [];
|
|
484
|
-
const candidate = records.find((record) => record.
|
|
490
|
+
const candidate = records.find((record) => record.sessionOwnerKey === session.ownerKey && !record.consumed);
|
|
485
491
|
if (!candidate) {
|
|
486
492
|
return undefined;
|
|
487
493
|
}
|
|
@@ -491,12 +497,12 @@ function consumeNextTimingCandidate(manager, session, input) {
|
|
|
491
497
|
/** Consume a timing candidate only when the public hook data makes it unambiguous. */
|
|
492
498
|
function consumeTimingCandidate(manager, session, event) {
|
|
493
499
|
const key = modelTimingLlmKey({
|
|
494
|
-
sessionId: session.
|
|
500
|
+
sessionId: session.ownerKey,
|
|
495
501
|
runId: event.runId,
|
|
496
502
|
provider: event.provider,
|
|
497
503
|
model: event.model,
|
|
498
504
|
});
|
|
499
|
-
const candidates = (manager.state.modelTimingsByLlmKey.get(key) ?? []).filter((record) => record.
|
|
505
|
+
const candidates = (manager.state.modelTimingsByLlmKey.get(key) ?? []).filter((record) => record.sessionOwnerKey === session.ownerKey && !record.consumed);
|
|
500
506
|
if (candidates.length === 1) {
|
|
501
507
|
const candidate = candidates[0];
|
|
502
508
|
if (!candidate) {
|
|
@@ -580,7 +586,7 @@ function emitModelTimingSummaryMark(manager, session, records) {
|
|
|
580
586
|
/** Convert an OpenClaw llm_input event into the buffered request record. */
|
|
581
587
|
function createInputRecord(session, event) {
|
|
582
588
|
return {
|
|
583
|
-
|
|
589
|
+
sessionOwnerKey: session.ownerKey,
|
|
584
590
|
sessionId: event.sessionId,
|
|
585
591
|
runId: event.runId,
|
|
586
592
|
provider: event.provider,
|
|
@@ -594,15 +600,23 @@ function createInputRecord(session, event) {
|
|
|
594
600
|
}
|
|
595
601
|
/** Resolve an existing session for before_message_write without creating a fake one. */
|
|
596
602
|
function existingSessionForMessageWrite(manager, event, ctx) {
|
|
597
|
-
const key =
|
|
603
|
+
const key = resolveSessionOwnerKey(manager.state, {
|
|
598
604
|
sessionKey: event.sessionKey ?? ctx.sessionKey,
|
|
599
605
|
});
|
|
600
|
-
|
|
606
|
+
if (key === undefined) {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
const session = manager.state.sessions.get(key);
|
|
610
|
+
const sessionKey = event.sessionKey ?? ctx.sessionKey;
|
|
611
|
+
if (session && session.sessionKey === undefined && sessionKey !== undefined) {
|
|
612
|
+
session.sessionKey = sessionKey;
|
|
613
|
+
}
|
|
614
|
+
return session;
|
|
601
615
|
}
|
|
602
616
|
/** Build a minimal request placeholder when only an llm_output hook is available. */
|
|
603
617
|
function placeholderInputRecord(record) {
|
|
604
618
|
return {
|
|
605
|
-
|
|
619
|
+
sessionOwnerKey: record.sessionOwnerKey,
|
|
606
620
|
sessionId: record.sessionId,
|
|
607
621
|
runId: record.runId,
|
|
608
622
|
provider: record.provider,
|
|
@@ -941,19 +955,33 @@ function incrementHookLlmOutputReplayCount(session, runId, maxRuns) {
|
|
|
941
955
|
}
|
|
942
956
|
/** Build the per-session run key used for trajectory de-duplication. */
|
|
943
957
|
function trajectoryRunKey(session, runId) {
|
|
944
|
-
return runId ?? session.
|
|
958
|
+
return runId ?? session.ownerKey;
|
|
945
959
|
}
|
|
946
|
-
/** Normalize provider usage into
|
|
960
|
+
/** Normalize provider usage into NeMo Relay token and cost fields. */
|
|
947
961
|
function mapUsage(usage) {
|
|
948
962
|
if (!isRecord(usage)) {
|
|
949
963
|
return undefined;
|
|
950
964
|
}
|
|
951
965
|
const mapped = {};
|
|
952
|
-
const input = numberField(usage, 'input') ??
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const
|
|
966
|
+
const input = numberField(usage, 'input') ??
|
|
967
|
+
numberField(usage, 'prompt_tokens') ??
|
|
968
|
+
numberField(usage, 'input_tokens') ??
|
|
969
|
+
numberField(usage, 'inputTokens');
|
|
970
|
+
const output = numberField(usage, 'output') ??
|
|
971
|
+
numberField(usage, 'completion_tokens') ??
|
|
972
|
+
numberField(usage, 'output_tokens') ??
|
|
973
|
+
numberField(usage, 'outputTokens');
|
|
974
|
+
const cacheRead = numberField(usage, 'cacheRead') ??
|
|
975
|
+
numberField(usage, 'cache_read_tokens') ??
|
|
976
|
+
numberField(usage, 'cache_read_input_tokens') ??
|
|
977
|
+
nestedNumberField(usage, 'input_tokens_details', 'cached_tokens') ??
|
|
978
|
+
nestedNumberField(usage, 'prompt_tokens_details', 'cached_tokens');
|
|
979
|
+
const cacheWrite = numberField(usage, 'cacheWrite') ??
|
|
980
|
+
numberField(usage, 'cache_write_tokens') ??
|
|
981
|
+
numberField(usage, 'cache_creation_input_tokens');
|
|
982
|
+
const total = numberField(usage, 'total') ??
|
|
983
|
+
numberField(usage, 'totalTokens') ??
|
|
984
|
+
numberField(usage, 'total_tokens');
|
|
957
985
|
const totalCanIncludeCompletion = total === undefined || output === undefined || total >= output;
|
|
958
986
|
const prompt = total !== undefined && output !== undefined && totalCanIncludeCompletion ? total - output : input;
|
|
959
987
|
const totalCanIncludePrompt = total === undefined || prompt === undefined || total >= prompt;
|
|
@@ -993,6 +1021,11 @@ function numberField(record, key) {
|
|
|
993
1021
|
const value = record[key];
|
|
994
1022
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
995
1023
|
}
|
|
1024
|
+
/** Read a finite numeric field nested one object deep from a generic hook record. */
|
|
1025
|
+
function nestedNumberField(record, objectKey, fieldKey) {
|
|
1026
|
+
const value = record[objectKey];
|
|
1027
|
+
return isRecord(value) ? numberField(value, fieldKey) : undefined;
|
|
1028
|
+
}
|
|
996
1029
|
/** Copy model_call_ended details into a retained timing record. */
|
|
997
1030
|
function applyModelCallEnd(record, event, nowMs) {
|
|
998
1031
|
record.observedAtMs = nowMs;
|
|
@@ -1015,7 +1048,7 @@ function latestUnendedRecord(records, session) {
|
|
|
1015
1048
|
}
|
|
1016
1049
|
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
1017
1050
|
const record = records[index];
|
|
1018
|
-
if (record?.
|
|
1051
|
+
if (record?.sessionOwnerKey === session.ownerKey && record.endedAtMs === undefined) {
|
|
1019
1052
|
return record;
|
|
1020
1053
|
}
|
|
1021
1054
|
}
|
|
@@ -6,7 +6,7 @@ export function emitMark(params) {
|
|
|
6
6
|
params.state.counters.skippedEvents += 1;
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
|
-
params.nf.event(params.name, params.session.rootHandle, params.data, null, params.timestamp ?? null);
|
|
9
|
+
params.nf.event(params.name, params.session.rootHandle, params.data, params.metadata ?? null, params.timestamp ?? null);
|
|
10
10
|
params.state.counters.marksEmitted += 1;
|
|
11
11
|
}
|
|
12
12
|
/** Return a blocked-tool event payload when OpenClaw denied the tool call. */
|
|
@@ -19,11 +19,16 @@ export type SessionLookupInput = {
|
|
|
19
19
|
};
|
|
20
20
|
export type EnsureSessionInput = SessionLookupInput & {
|
|
21
21
|
agentId?: string | undefined;
|
|
22
|
+
parentHandle?: ReturnType<NemoRelayRuntimeModule['pushScope']> | undefined;
|
|
23
|
+
scopeRole?: 'subagent' | undefined;
|
|
22
24
|
source: 'session_start' | 'lazy_session';
|
|
23
25
|
resumedFrom?: string | undefined;
|
|
24
26
|
timestamp?: number | undefined;
|
|
27
|
+
deferRootOpen?: boolean | undefined;
|
|
25
28
|
};
|
|
26
29
|
export type SessionState = {
|
|
30
|
+
/** Immutable internal owner key used for replay buffers and alias lookups. */
|
|
31
|
+
ownerKey: string;
|
|
27
32
|
sessionId: string;
|
|
28
33
|
sessionKey?: string;
|
|
29
34
|
agentId?: string;
|
|
@@ -42,9 +47,17 @@ export type SessionState = {
|
|
|
42
47
|
assistantMessageWrites?: AssistantMessageRecord[];
|
|
43
48
|
stack: ReturnType<NemoRelayRuntimeModule['createScopeStack']>;
|
|
44
49
|
rootHandle?: ReturnType<NemoRelayRuntimeModule['pushScope']>;
|
|
50
|
+
scopeRole?: 'subagent';
|
|
51
|
+
pendingRootOpen?: boolean;
|
|
52
|
+
pendingRootTimestampMicros?: number;
|
|
53
|
+
pendingRootRunId?: string;
|
|
54
|
+
pendingCapturedEmits?: Array<{
|
|
55
|
+
label: string;
|
|
56
|
+
emit: () => void;
|
|
57
|
+
}>;
|
|
45
58
|
};
|
|
46
59
|
export type PendingLlmOutputRecord = {
|
|
47
|
-
|
|
60
|
+
sessionOwnerKey: string;
|
|
48
61
|
sessionId: string;
|
|
49
62
|
runId: string;
|
|
50
63
|
provider: string;
|
|
@@ -55,7 +68,7 @@ export type PendingLlmOutputRecord = {
|
|
|
55
68
|
timer?: ReturnType<typeof setTimeout> | undefined;
|
|
56
69
|
};
|
|
57
70
|
export type LlmInputRecord = {
|
|
58
|
-
|
|
71
|
+
sessionOwnerKey: string;
|
|
59
72
|
sessionId: string;
|
|
60
73
|
runId: string;
|
|
61
74
|
provider: string;
|
|
@@ -68,7 +81,7 @@ export type LlmInputRecord = {
|
|
|
68
81
|
placeholderRequest?: boolean | undefined;
|
|
69
82
|
};
|
|
70
83
|
export type AssistantMessageRecord = {
|
|
71
|
-
|
|
84
|
+
sessionOwnerKey: string;
|
|
72
85
|
provider: string;
|
|
73
86
|
model: string;
|
|
74
87
|
assistantTexts: string[];
|
|
@@ -80,7 +93,7 @@ export type AssistantMessageRecord = {
|
|
|
80
93
|
usage?: unknown;
|
|
81
94
|
};
|
|
82
95
|
export type ModelCallRecord = {
|
|
83
|
-
|
|
96
|
+
sessionOwnerKey: string;
|
|
84
97
|
runId: string;
|
|
85
98
|
callId: string;
|
|
86
99
|
provider: string;
|
|
@@ -130,23 +143,26 @@ export type SessionManager = {
|
|
|
130
143
|
}) => void;
|
|
131
144
|
emitUnpairedModelCallTimingMarks: (session: SessionState) => void;
|
|
132
145
|
logBoundedWarn: (key: string, message: string) => void;
|
|
146
|
+
resolveSessionRootContext?: (input: EnsureSessionInput) => Partial<EnsureSessionInput> | undefined;
|
|
133
147
|
};
|
|
134
148
|
/** Return all keys that may identify an existing OpenClaw session. */
|
|
135
149
|
export declare function lookupSessionKeys(input: SessionLookupInput): string[];
|
|
136
|
-
/** Return keys that should alias to
|
|
150
|
+
/** Return keys that should alias to one stable internal session owner. */
|
|
137
151
|
export declare function aliasSessionKeys(input: SessionLookupInput): string[];
|
|
138
|
-
/** Resolve a hook's session identity to the
|
|
139
|
-
export declare function
|
|
140
|
-
/** Remember equivalent hook identifiers so later events attach to the same root span. */
|
|
152
|
+
/** Resolve a hook's session identity to the stable owner key used in replay state. */
|
|
153
|
+
export declare function resolveSessionOwnerKey(state: HookReplayBackendState, input: SessionLookupInput): string | undefined;
|
|
154
|
+
/** Remember equivalent hook identifiers so later events attach to the same owner and root span. */
|
|
141
155
|
export declare function rememberSessionAliases(state: HookReplayBackendState, session: SessionState, input: SessionLookupInput): void;
|
|
142
156
|
/** Create the mutable in-memory state used by the hook replay backend. */
|
|
143
157
|
export declare function createHookReplayState(): HookReplayBackendState;
|
|
144
158
|
/** Return an existing session or lazily create a root session scope for replay. */
|
|
145
159
|
export declare function ensureSession(manager: SessionManager, input: EnsureSessionInput): SessionState | undefined;
|
|
160
|
+
/** Queue an emit callback until a deferred session root can open with honest lineage. */
|
|
161
|
+
export declare function queueCapturedEmit(session: SessionState, label: string, emit: () => void): boolean;
|
|
146
162
|
/** Flush pending LLM output/timing state before the root session closes. */
|
|
147
163
|
export declare function drainSession(manager: SessionManager, session: SessionState): void;
|
|
148
164
|
/** Close the root session scope with separate lifecycle summary and user-visible output. */
|
|
149
|
-
export declare function closeSessionRoot(manager: SessionManager, session: SessionState, summary: JsonRecord, rootOutput?: JsonRecord, timestamp?: number): void;
|
|
165
|
+
export declare function closeSessionRoot(manager: SessionManager, session: SessionState, summary: JsonRecord, rootOutput?: JsonRecord, metadata?: JsonRecord | null, timestamp?: number): void;
|
|
150
166
|
/** Remove a closed session from active replay state. */
|
|
151
167
|
export declare function deleteSession(state: HookReplayBackendState, session: SessionState): void;
|
|
152
168
|
/** Insert a correlation record while bounding retained entries per key. */
|
|
@@ -155,3 +171,5 @@ export declare function insertBoundedRecord<T>(map: Map<string, T[]>, key: strin
|
|
|
155
171
|
export declare function tupleKey(parts: Array<string | undefined>): string;
|
|
156
172
|
/** Evict stale cross-hook correlation records across all replay maps. */
|
|
157
173
|
export declare function evictExpiredCorrelationRecords(state: HookReplayBackendState, nowMs: number, ttlMs: number): void;
|
|
174
|
+
/** Open a deferred or new root session scope and flush queued child emissions. */
|
|
175
|
+
export declare function materializeSessionRoot(manager: SessionManager, session: SessionState, input: EnsureSessionInput): void;
|