@senpi/runtime 3.0.6-dev.runtime-phase-2.20260630134307 → 3.0.6-dev.runtime-phase-2.20260630134410
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/actions/close-position/close-position.action.d.ts +1 -1
- package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
- package/dist/actions/close-position/close-position.action.js +15 -14
- package/dist/actions/close-position/close-position.action.js.map +1 -1
- package/dist/actions/event-derivation.d.ts +60 -0
- package/dist/actions/event-derivation.d.ts.map +1 -0
- package/dist/actions/event-derivation.js +179 -0
- package/dist/actions/event-derivation.js.map +1 -0
- package/dist/actions/llm-decision/anthropic-llm-decision.d.ts +5 -9
- package/dist/actions/llm-decision/anthropic-llm-decision.d.ts.map +1 -1
- package/dist/actions/llm-decision/anthropic-llm-decision.js +6 -87
- package/dist/actions/llm-decision/anthropic-llm-decision.js.map +1 -1
- package/dist/actions/llm-decision/factory.d.ts +0 -6
- package/dist/actions/llm-decision/factory.d.ts.map +1 -1
- package/dist/actions/llm-decision/factory.js +0 -2
- package/dist/actions/llm-decision/factory.js.map +1 -1
- package/dist/actions/llm-decision/openclaw-llm-decision.d.ts.map +1 -1
- package/dist/actions/llm-decision/openclaw-llm-decision.js +21 -2
- package/dist/actions/llm-decision/openclaw-llm-decision.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts +1 -1
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +28 -21
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.js +2 -4
- package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
- package/dist/actions/tracking-helpers.d.ts +41 -3
- package/dist/actions/tracking-helpers.d.ts.map +1 -1
- package/dist/actions/tracking-helpers.js +100 -11
- package/dist/actions/tracking-helpers.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +8 -0
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +36 -2
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -13
- package/dist/index.js.map +1 -1
- package/dist/recording/otel/attributes.d.ts +2 -0
- package/dist/recording/otel/attributes.d.ts.map +1 -1
- package/dist/recording/otel/attributes.js +1 -1
- package/dist/recording/otel/attributes.js.map +1 -1
- package/dist/recording/otel/mappers.d.ts.map +1 -1
- package/dist/recording/otel/mappers.js +1 -0
- package/dist/recording/otel/mappers.js.map +1 -1
- package/dist/recording/records.d.ts +7 -1
- package/dist/recording/records.d.ts.map +1 -1
- package/dist/recording/wiring/emitter-factory.d.ts +0 -5
- package/dist/recording/wiring/emitter-factory.d.ts.map +1 -1
- package/dist/recording/wiring/emitter-factory.js +0 -18
- package/dist/recording/wiring/emitter-factory.js.map +1 -1
- package/dist/recording/wiring/index.d.ts +1 -1
- package/dist/recording/wiring/index.d.ts.map +1 -1
- package/dist/recording/wiring/index.js +1 -1
- package/dist/recording/wiring/index.js.map +1 -1
- package/dist/recording/wiring/record-builders.d.ts.map +1 -1
- package/dist/recording/wiring/record-builders.js +5 -2
- package/dist/recording/wiring/record-builders.js.map +1 -1
- package/dist/runtime/auto-update/coordinator.js +2 -2
- package/dist/runtime/auto-update/coordinator.js.map +1 -1
- package/dist/runtime/create-actions.d.ts.map +1 -1
- package/dist/runtime/create-actions.js +0 -10
- package/dist/runtime/create-actions.js.map +1 -1
- package/dist/runtime/domain-events.d.ts +43 -0
- package/dist/runtime/domain-events.d.ts.map +1 -0
- package/dist/runtime/domain-events.js +181 -0
- package/dist/runtime/domain-events.js.map +1 -0
- package/dist/runtime/lifecycle-events.d.ts +43 -0
- package/dist/runtime/lifecycle-events.d.ts.map +1 -0
- package/dist/runtime/lifecycle-events.js +139 -0
- package/dist/runtime/lifecycle-events.js.map +1 -0
- package/dist/runtime/notifications.js +2 -2
- package/dist/runtime/notifications.js.map +1 -1
- package/dist/runtime/runtime-schema.d.ts +124 -173
- package/dist/runtime/runtime-schema.d.ts.map +1 -1
- package/dist/runtime/runtime-schema.js +4 -4
- package/dist/runtime/runtime-schema.js.map +1 -1
- package/dist/runtime/runtime.d.ts +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +62 -52
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/strategy-lifecycle.d.ts.map +1 -1
- package/dist/runtime/strategy-lifecycle.js +13 -1
- package/dist/runtime/strategy-lifecycle.js.map +1 -1
- package/dist/runtime-api/routes/signals.d.ts +1 -1
- package/dist/runtime-api/routes/signals.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.js +2 -8
- package/dist/runtime-api/routes/signals.js.map +1 -1
- package/dist/senpi/client.d.ts +10 -0
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +71 -49
- package/dist/senpi/client.js.map +1 -1
- package/dist/skills-manager/coordinator.js +3 -3
- package/dist/skills-manager/coordinator.js.map +1 -1
- package/dist/telemetry/metrics.d.ts +47 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +154 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/otel-sdk.d.ts +23 -35
- package/dist/telemetry/otel-sdk.d.ts.map +1 -1
- package/dist/telemetry/otel-sdk.js +83 -54
- package/dist/telemetry/otel-sdk.js.map +1 -1
- package/dist/telemetry/trace-identity.d.ts +2 -1
- package/dist/telemetry/trace-identity.d.ts.map +1 -1
- package/dist/telemetry/trace-identity.js +2 -1
- package/dist/telemetry/trace-identity.js.map +1 -1
- package/dist/telemetry/tracing.d.ts +2 -1
- package/dist/telemetry/tracing.d.ts.map +1 -1
- package/dist/telemetry/tracing.js +1 -1
- package/dist/telemetry/tracing.js.map +1 -1
- package/dist/tracking/context.d.ts +6 -0
- package/dist/tracking/context.d.ts.map +1 -1
- package/dist/tracking/context.js.map +1 -1
- package/dist/tracking/spans/constants.d.ts +4 -11
- package/dist/tracking/spans/constants.d.ts.map +1 -1
- package/dist/tracking/spans/constants.js +4 -23
- package/dist/tracking/spans/constants.js.map +1 -1
- package/dist/tracking/spans/gen-ai.d.ts +70 -0
- package/dist/tracking/spans/gen-ai.d.ts.map +1 -0
- package/dist/tracking/spans/gen-ai.js +330 -0
- package/dist/tracking/spans/gen-ai.js.map +1 -0
- package/dist/tracking/spans/index.d.ts +6 -8
- package/dist/tracking/spans/index.d.ts.map +1 -1
- package/dist/tracking/spans/index.js +6 -11
- package/dist/tracking/spans/index.js.map +1 -1
- package/dist/tracking/spans/redact-pii.d.ts +19 -21
- package/dist/tracking/spans/redact-pii.d.ts.map +1 -1
- package/dist/tracking/spans/redact-pii.js +31 -31
- package/dist/tracking/spans/redact-pii.js.map +1 -1
- package/dist/tracking/spans/span-hooks.d.ts +0 -17
- package/dist/tracking/spans/span-hooks.d.ts.map +1 -1
- package/dist/tracking/spans/span-hooks.js +407 -207
- package/dist/tracking/spans/span-hooks.js.map +1 -1
- package/dist/utils/event-catalog.d.ts +269 -0
- package/dist/utils/event-catalog.d.ts.map +1 -0
- package/dist/utils/event-catalog.js +91 -0
- package/dist/utils/event-catalog.js.map +1 -0
- package/dist/utils/logger.d.ts +58 -5
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +359 -27
- package/dist/utils/logger.js.map +1 -1
- package/package.json +2 -1
- package/dist/tracking/spans/fan-out-span-transport.d.ts +0 -22
- package/dist/tracking/spans/fan-out-span-transport.d.ts.map +0 -1
- package/dist/tracking/spans/fan-out-span-transport.js +0 -49
- package/dist/tracking/spans/fan-out-span-transport.js.map +0 -1
- package/dist/tracking/spans/otel/fit-row-budget.d.ts +0 -22
- package/dist/tracking/spans/otel/fit-row-budget.d.ts.map +0 -1
- package/dist/tracking/spans/otel/fit-row-budget.js +0 -91
- package/dist/tracking/spans/otel/fit-row-budget.js.map +0 -1
- package/dist/tracking/spans/otel/map-span-to-trace-row.d.ts +0 -16
- package/dist/tracking/spans/otel/map-span-to-trace-row.d.ts.map +0 -1
- package/dist/tracking/spans/otel/map-span-to-trace-row.js +0 -227
- package/dist/tracking/spans/otel/map-span-to-trace-row.js.map +0 -1
- package/dist/tracking/spans/otel/recording-span-transport.d.ts +0 -43
- package/dist/tracking/spans/otel/recording-span-transport.d.ts.map +0 -1
- package/dist/tracking/spans/otel/recording-span-transport.js +0 -67
- package/dist/tracking/spans/otel/recording-span-transport.js.map +0 -1
- package/dist/tracking/spans/otel/span-ids.d.ts +0 -22
- package/dist/tracking/spans/otel/span-ids.d.ts.map +0 -1
- package/dist/tracking/spans/otel/span-ids.js +0 -32
- package/dist/tracking/spans/otel/span-ids.js.map +0 -1
- package/dist/tracking/spans/plugin-span-tracker.d.ts +0 -9
- package/dist/tracking/spans/plugin-span-tracker.d.ts.map +0 -1
- package/dist/tracking/spans/plugin-span-tracker.js +0 -115
- package/dist/tracking/spans/plugin-span-tracker.js.map +0 -1
- package/dist/tracking/spans/span-tracker.d.ts +0 -133
- package/dist/tracking/spans/span-tracker.d.ts.map +0 -1
- package/dist/tracking/spans/span-tracker.js +0 -721
- package/dist/tracking/spans/span-tracker.js.map +0 -1
- package/dist/tracking/spans/span-transport.d.ts +0 -71
- package/dist/tracking/spans/span-transport.d.ts.map +0 -1
- package/dist/tracking/spans/span-transport.js +0 -240
- package/dist/tracking/spans/span-transport.js.map +0 -1
- package/dist/tracking/spans/types.d.ts +0 -101
- package/dist/tracking/spans/types.d.ts.map +0 -1
- package/dist/tracking/spans/types.js +0 -6
- package/dist/tracking/spans/types.js.map +0 -1
|
@@ -1,721 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SpanTracker: stitches start/end hook pairs into one finalized span and
|
|
3
|
-
* enqueues it on the transport. Maintains two registries:
|
|
4
|
-
*
|
|
5
|
-
* - openSpans: Map<spanKey, OpenSpan> (LLM and tool spans)
|
|
6
|
-
* - openSubagents: Map<childSessionKey, OpenSubagentSpan>
|
|
7
|
-
*
|
|
8
|
-
* Lifetimes:
|
|
9
|
-
* - LLM span: llm_input → llm_output (single host turn)
|
|
10
|
-
* - Tool span: before_tool_call → after_tool_call (single tool call)
|
|
11
|
-
* - Subagent: subagent_spawning → … → subagent_ended (may outlive host turns)
|
|
12
|
-
*
|
|
13
|
-
* Orphan handling — flush spans we couldn't pair up with `data.orphan_reason`:
|
|
14
|
-
* - agent_end sweep: any span open under (session,run) when agent_end fires
|
|
15
|
-
* - stale sweep: anything older than STALE_TIMEOUT_MS (periodic)
|
|
16
|
-
* - shutdown drain: anything still open at shutdown
|
|
17
|
-
*
|
|
18
|
-
* Never throws — errors are caught and logged.
|
|
19
|
-
*/
|
|
20
|
-
import { randomUUID } from "node:crypto";
|
|
21
|
-
import { createLogger } from "../../utils/logger.js";
|
|
22
|
-
import { STALE_TIMEOUT_MS, SWEEP_INTERVAL_MS, } from "./constants.js";
|
|
23
|
-
const logger = createLogger("[senpi-core][span-tracker]");
|
|
24
|
-
const ADDRESS_RE = /^0x[a-f0-9]{40}$/i;
|
|
25
|
-
const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
26
|
-
function spanKeyLlm(sessionKey, runId) {
|
|
27
|
-
return `session:${sessionKey}:run:${runId}:llm`;
|
|
28
|
-
}
|
|
29
|
-
function spanKeyTool(sessionKey, runId, toolCallId, toolName, seq) {
|
|
30
|
-
if (toolCallId) {
|
|
31
|
-
return `session:${sessionKey}:run:${runId}:toolcall:${toolCallId}`;
|
|
32
|
-
}
|
|
33
|
-
return `session:${sessionKey}:run:${runId}:tool:${toolName}:${seq}`;
|
|
34
|
-
}
|
|
35
|
-
function sanitizeCorrelationId(raw) {
|
|
36
|
-
if (typeof raw !== "string")
|
|
37
|
-
return null;
|
|
38
|
-
return UUID_V4_RE.test(raw) ? raw : null;
|
|
39
|
-
}
|
|
40
|
-
function sanitizeAddress(raw) {
|
|
41
|
-
if (typeof raw !== "string")
|
|
42
|
-
return "";
|
|
43
|
-
const lowered = raw.toLowerCase();
|
|
44
|
-
return ADDRESS_RE.test(lowered) ? lowered : "";
|
|
45
|
-
}
|
|
46
|
-
function sanitizeString(raw) {
|
|
47
|
-
return typeof raw === "string" && raw.length > 0 ? raw : "";
|
|
48
|
-
}
|
|
49
|
-
function sanitizeUint(raw) {
|
|
50
|
-
return typeof raw === "number" && Number.isInteger(raw) && raw >= 0 ? raw : null;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Format a number as a fixed-6-decimal-place string for the cost_usd wire
|
|
54
|
-
* field. Returns null when input is absent or non-numeric. The schema expects
|
|
55
|
-
* `^\d+(\.\d{1,6})?$` — we emit canonical 6-decimal form so all wire values
|
|
56
|
-
* are consistent. Negative values are rejected (return null) to match the
|
|
57
|
-
* server-side validator.
|
|
58
|
-
*/
|
|
59
|
-
function sanitizeCostUsd(raw) {
|
|
60
|
-
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0)
|
|
61
|
-
return null;
|
|
62
|
-
return raw.toFixed(6);
|
|
63
|
-
}
|
|
64
|
-
export class SpanTracker {
|
|
65
|
-
transport;
|
|
66
|
-
now;
|
|
67
|
-
staleTimeoutMs;
|
|
68
|
-
openSpans = new Map();
|
|
69
|
-
openSubagents = new Map();
|
|
70
|
-
toolSeq = 0;
|
|
71
|
-
sweepTimer = null;
|
|
72
|
-
constructor(options) {
|
|
73
|
-
this.transport = options.transport;
|
|
74
|
-
this.now = options.now ?? Date.now;
|
|
75
|
-
this.staleTimeoutMs = options.staleTimeoutMs ?? STALE_TIMEOUT_MS;
|
|
76
|
-
const sweepInterval = options.sweepIntervalMs ?? SWEEP_INTERVAL_MS;
|
|
77
|
-
if (options.enableSweeper !== false) {
|
|
78
|
-
this.sweepTimer = setInterval(() => {
|
|
79
|
-
this.sweepStale();
|
|
80
|
-
}, sweepInterval);
|
|
81
|
-
if (this.sweepTimer && typeof this.sweepTimer === "object" && "unref" in this.sweepTimer) {
|
|
82
|
-
this.sweepTimer.unref();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// ─── LLM lifecycle (hook path) ──────────────────────────────────────────
|
|
87
|
-
openLlmSpan(input) {
|
|
88
|
-
try {
|
|
89
|
-
const key = spanKeyLlm(input.sessionKey, input.runId);
|
|
90
|
-
const existing = this.openSpans.get(key);
|
|
91
|
-
if (existing) {
|
|
92
|
-
// Silently discard the prior open span. A duplicate llm_input for the
|
|
93
|
-
// same (session, run) usually means OpenClaw's agent loop is starting
|
|
94
|
-
// a new turn (e.g., feeding back a tool result) without having fired
|
|
95
|
-
// llm_output for the previous turn — emitting an orphan row would
|
|
96
|
-
// pollute ClickHouse with partial records. We post only after the
|
|
97
|
-
// matching llm_output completes the merge.
|
|
98
|
-
logger.debug("openLlmSpan: discarding prior open span", {
|
|
99
|
-
sessionKey: input.sessionKey,
|
|
100
|
-
runId: input.runId,
|
|
101
|
-
});
|
|
102
|
-
this.openSpans.delete(key);
|
|
103
|
-
}
|
|
104
|
-
const id = randomUUID();
|
|
105
|
-
this.openSpans.set(key, {
|
|
106
|
-
id,
|
|
107
|
-
spanType: "llm",
|
|
108
|
-
sessionKey: input.sessionKey,
|
|
109
|
-
runId: input.runId,
|
|
110
|
-
parentSpanId: "",
|
|
111
|
-
toolCallId: "",
|
|
112
|
-
startedAt: input.startedAt ?? this.now(),
|
|
113
|
-
correlationId: sanitizeCorrelationId(input.correlationId),
|
|
114
|
-
address: sanitizeAddress(input.address),
|
|
115
|
-
recipeName: sanitizeString(input.recipeName),
|
|
116
|
-
runtimeId: sanitizeString(input.runtimeId),
|
|
117
|
-
runtimeVersion: sanitizeString(input.runtimeVersion),
|
|
118
|
-
data: { input: input.input },
|
|
119
|
-
emissionSource: input.emissionSource,
|
|
120
|
-
});
|
|
121
|
-
return id;
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
logger.warn("openLlmSpan failed", { err: errMsg(err) });
|
|
125
|
-
return "";
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
finalizeLlmSpan(input) {
|
|
129
|
-
try {
|
|
130
|
-
const key = spanKeyLlm(input.sessionKey, input.runId);
|
|
131
|
-
const open = this.openSpans.get(key);
|
|
132
|
-
if (!open) {
|
|
133
|
-
logger.warn("finalizeLlmSpan: no open span", {
|
|
134
|
-
sessionKey: input.sessionKey,
|
|
135
|
-
runId: input.runId,
|
|
136
|
-
});
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
this.openSpans.delete(key);
|
|
140
|
-
const endedAt = this.now();
|
|
141
|
-
const data = {
|
|
142
|
-
...open.data,
|
|
143
|
-
...(input.output !== undefined ? { output: input.output } : {}),
|
|
144
|
-
...(input.error !== undefined ? { error: errMsg(input.error) } : {}),
|
|
145
|
-
};
|
|
146
|
-
this.transport.enqueue({
|
|
147
|
-
id: open.id,
|
|
148
|
-
correlationId: sanitizeCorrelationId(input.correlationId) ?? open.correlationId,
|
|
149
|
-
sessionKey: open.sessionKey,
|
|
150
|
-
runId: open.runId,
|
|
151
|
-
childSessionKey: "",
|
|
152
|
-
toolCallId: "",
|
|
153
|
-
parentSpanId: open.parentSpanId,
|
|
154
|
-
spanType: "llm",
|
|
155
|
-
emissionSource: open.emissionSource,
|
|
156
|
-
timestamp: endedAt,
|
|
157
|
-
address: sanitizeAddress(input.address) || open.address,
|
|
158
|
-
recipeName: sanitizeString(input.recipeName) || open.recipeName,
|
|
159
|
-
runtimeId: sanitizeString(input.runtimeId) || open.runtimeId,
|
|
160
|
-
runtimeVersion: sanitizeString(input.runtimeVersion) || open.runtimeVersion,
|
|
161
|
-
startedAt: open.startedAt,
|
|
162
|
-
endedAt,
|
|
163
|
-
durationMs: Math.max(0, endedAt - open.startedAt),
|
|
164
|
-
model: sanitizeString(input.model),
|
|
165
|
-
tokensIn: sanitizeUint(input.tokensIn),
|
|
166
|
-
tokensOut: sanitizeUint(input.tokensOut),
|
|
167
|
-
costUsd: sanitizeCostUsd(input.costUsd),
|
|
168
|
-
data,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
logger.warn("finalizeLlmSpan failed", { err: errMsg(err) });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/** Returns the id of the currently-open LLM span for (session, run), or null. */
|
|
176
|
-
getActiveLlmSpanId(sessionKey, runId) {
|
|
177
|
-
return this.openSpans.get(spanKeyLlm(sessionKey, runId))?.id ?? null;
|
|
178
|
-
}
|
|
179
|
-
// ─── Tool lifecycle (hook path) ─────────────────────────────────────────
|
|
180
|
-
openToolSpan(input) {
|
|
181
|
-
try {
|
|
182
|
-
const parentSpanId = this.getActiveLlmSpanId(input.sessionKey, input.runId);
|
|
183
|
-
const seq = ++this.toolSeq;
|
|
184
|
-
const key = spanKeyTool(input.sessionKey, input.runId, input.toolCallId, input.toolName, seq);
|
|
185
|
-
// For toolCallId-keyed entries, silently discard any duplicate so we
|
|
186
|
-
// don't emit partial rows. The merge happens at after_tool_call.
|
|
187
|
-
if (input.toolCallId) {
|
|
188
|
-
const existing = this.openSpans.get(key);
|
|
189
|
-
if (existing) {
|
|
190
|
-
logger.debug("openToolSpan: discarding prior open span", {
|
|
191
|
-
toolCallId: input.toolCallId,
|
|
192
|
-
});
|
|
193
|
-
this.openSpans.delete(key);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const id = randomUUID();
|
|
197
|
-
this.openSpans.set(key, {
|
|
198
|
-
id,
|
|
199
|
-
spanType: "tool",
|
|
200
|
-
sessionKey: input.sessionKey,
|
|
201
|
-
runId: input.runId,
|
|
202
|
-
parentSpanId: parentSpanId ?? "",
|
|
203
|
-
toolCallId: input.toolCallId ?? "",
|
|
204
|
-
startedAt: input.startedAt ?? this.now(),
|
|
205
|
-
correlationId: sanitizeCorrelationId(input.correlationId),
|
|
206
|
-
address: sanitizeAddress(input.address),
|
|
207
|
-
recipeName: sanitizeString(input.recipeName),
|
|
208
|
-
runtimeId: sanitizeString(input.runtimeId),
|
|
209
|
-
runtimeVersion: sanitizeString(input.runtimeVersion),
|
|
210
|
-
// Flat shape matches captureToolSpan (direct path) so all
|
|
211
|
-
// tool-typed rows have identical `data` layout:
|
|
212
|
-
// { tool_name, input?, output?, error? }
|
|
213
|
-
// Downstream queries can rely on JSONExtractString(data, 'tool_name')
|
|
214
|
-
// regardless of emission_source.
|
|
215
|
-
data: {
|
|
216
|
-
tool_name: input.toolName,
|
|
217
|
-
...(input.toolInput !== undefined ? { input: input.toolInput } : {}),
|
|
218
|
-
},
|
|
219
|
-
emissionSource: input.emissionSource,
|
|
220
|
-
});
|
|
221
|
-
return id;
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
logger.warn("openToolSpan failed", { err: errMsg(err) });
|
|
225
|
-
return "";
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
finalizeToolSpan(input) {
|
|
229
|
-
try {
|
|
230
|
-
// Find by toolCallId first (most precise — bridges any runId drift).
|
|
231
|
-
let foundKey;
|
|
232
|
-
if (input.toolCallId) {
|
|
233
|
-
const exactKey = spanKeyTool(input.sessionKey, input.runId, input.toolCallId, input.toolName, 0);
|
|
234
|
-
if (this.openSpans.has(exactKey)) {
|
|
235
|
-
foundKey = exactKey;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
// Next, when we have a toolCallId: (session, *, toolCallId) — bridges
|
|
239
|
-
// runId drift between before_tool_call (runId=R1) and after_tool_call
|
|
240
|
-
// (runId=R2, e.g. when a fresh llm_input fired mid-tool and updated the
|
|
241
|
-
// session's lastActive runId). toolCallId is provider-stamped and unique
|
|
242
|
-
// per call, so this match is still precise — only the run scope shifts.
|
|
243
|
-
if (!foundKey && input.toolCallId) {
|
|
244
|
-
const sessionPrefix = `session:${input.sessionKey}:run:`;
|
|
245
|
-
const toolCallSuffix = `:toolcall:${input.toolCallId}`;
|
|
246
|
-
for (const key of this.openSpans.keys()) {
|
|
247
|
-
if (key.startsWith(sessionPrefix) && key.endsWith(toolCallSuffix)) {
|
|
248
|
-
foundKey = key;
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
// Next: (session, run, toolName) prefix — happy path when runId stable
|
|
254
|
-
// and no toolCallId was issued.
|
|
255
|
-
//
|
|
256
|
-
// Without a toolCallId we can't disambiguate two same-name tools open at
|
|
257
|
-
// once. We pick the oldest by insertion order (Map iteration order) and
|
|
258
|
-
// log a warn when multiple match so the ambiguity is observable in
|
|
259
|
-
// production rather than silently mis-attributing output. If providers
|
|
260
|
-
// ever emit parallel tool calls without ids, this surfaces in the logs.
|
|
261
|
-
if (!foundKey) {
|
|
262
|
-
const prefix = `session:${input.sessionKey}:run:${input.runId}:tool:${input.toolName}:`;
|
|
263
|
-
const matches = [];
|
|
264
|
-
for (const key of this.openSpans.keys()) {
|
|
265
|
-
if (key.startsWith(prefix))
|
|
266
|
-
matches.push(key);
|
|
267
|
-
}
|
|
268
|
-
if (matches.length > 1) {
|
|
269
|
-
logger.warn("finalizeToolSpan: ambiguous match (multiple same-name tools open without toolCallId); attributing to oldest", {
|
|
270
|
-
sessionKey: input.sessionKey,
|
|
271
|
-
runId: input.runId,
|
|
272
|
-
toolName: input.toolName,
|
|
273
|
-
matchCount: matches.length,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
foundKey = matches[0];
|
|
277
|
-
}
|
|
278
|
-
// Last resort: (session, *, toolName) — handles runId drift for spans
|
|
279
|
-
// without any toolCallId. Common with long-running exec tools. Same
|
|
280
|
-
// ambiguity rule: take the oldest matching open span and warn if more
|
|
281
|
-
// than one matched.
|
|
282
|
-
if (!foundKey && !input.toolCallId) {
|
|
283
|
-
const sessionPrefix = `session:${input.sessionKey}:run:`;
|
|
284
|
-
const toolSuffix = `:tool:${input.toolName}:`;
|
|
285
|
-
const matches = [];
|
|
286
|
-
for (const key of this.openSpans.keys()) {
|
|
287
|
-
if (key.startsWith(sessionPrefix) && key.includes(toolSuffix)) {
|
|
288
|
-
matches.push(key);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (matches.length > 1) {
|
|
292
|
-
logger.warn("finalizeToolSpan: ambiguous match across runIds (multiple same-name tools open without toolCallId); attributing to oldest", {
|
|
293
|
-
sessionKey: input.sessionKey,
|
|
294
|
-
toolName: input.toolName,
|
|
295
|
-
matchCount: matches.length,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
foundKey = matches[0];
|
|
299
|
-
}
|
|
300
|
-
if (!foundKey) {
|
|
301
|
-
// Likely already orphan-flushed by an agent_end sweep, or never opened.
|
|
302
|
-
// Downgrade to debug — warn was too noisy in practice.
|
|
303
|
-
logger.debug("finalizeToolSpan: no open span", {
|
|
304
|
-
sessionKey: input.sessionKey,
|
|
305
|
-
toolName: input.toolName,
|
|
306
|
-
toolCallId: input.toolCallId,
|
|
307
|
-
});
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const open = this.openSpans.get(foundKey);
|
|
311
|
-
if (!open)
|
|
312
|
-
return;
|
|
313
|
-
this.openSpans.delete(foundKey);
|
|
314
|
-
const endedAt = this.now();
|
|
315
|
-
const data = { ...open.data };
|
|
316
|
-
if (input.error !== undefined) {
|
|
317
|
-
data.error = errMsg(input.error);
|
|
318
|
-
}
|
|
319
|
-
else if (input.output !== undefined) {
|
|
320
|
-
data.output =
|
|
321
|
-
input.output && typeof input.output === "object"
|
|
322
|
-
? input.output
|
|
323
|
-
: { result: input.output };
|
|
324
|
-
}
|
|
325
|
-
this.transport.enqueue({
|
|
326
|
-
id: open.id,
|
|
327
|
-
correlationId: sanitizeCorrelationId(input.correlationId) ?? open.correlationId,
|
|
328
|
-
sessionKey: open.sessionKey,
|
|
329
|
-
runId: open.runId,
|
|
330
|
-
childSessionKey: "",
|
|
331
|
-
toolCallId: open.toolCallId,
|
|
332
|
-
parentSpanId: open.parentSpanId,
|
|
333
|
-
spanType: "tool",
|
|
334
|
-
emissionSource: open.emissionSource,
|
|
335
|
-
timestamp: endedAt,
|
|
336
|
-
address: sanitizeAddress(input.address) || open.address,
|
|
337
|
-
recipeName: sanitizeString(input.recipeName) || open.recipeName,
|
|
338
|
-
runtimeId: sanitizeString(input.runtimeId) || open.runtimeId,
|
|
339
|
-
runtimeVersion: sanitizeString(input.runtimeVersion) || open.runtimeVersion,
|
|
340
|
-
startedAt: open.startedAt,
|
|
341
|
-
endedAt,
|
|
342
|
-
durationMs: input.durationMs ?? Math.max(0, endedAt - open.startedAt),
|
|
343
|
-
model: "",
|
|
344
|
-
tokensIn: null,
|
|
345
|
-
tokensOut: null,
|
|
346
|
-
costUsd: null,
|
|
347
|
-
data,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
catch (err) {
|
|
351
|
-
logger.warn("finalizeToolSpan failed", { err: errMsg(err) });
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
// ─── Subagent lifecycle (separate registry, keyed by childSessionKey) ───
|
|
355
|
-
openSubagentSpan(input) {
|
|
356
|
-
try {
|
|
357
|
-
const existing = this.openSubagents.get(input.childSessionKey);
|
|
358
|
-
if (existing) {
|
|
359
|
-
this.enqueueSubagentOrphan(existing, "subagent_spawning_replaces_active");
|
|
360
|
-
this.openSubagents.delete(input.childSessionKey);
|
|
361
|
-
}
|
|
362
|
-
const id = randomUUID();
|
|
363
|
-
this.openSubagents.set(input.childSessionKey, {
|
|
364
|
-
id,
|
|
365
|
-
hostSessionKey: input.hostSessionKey,
|
|
366
|
-
hostRunId: input.hostRunId,
|
|
367
|
-
childSessionKey: input.childSessionKey,
|
|
368
|
-
parentSpanId: input.parentSpanId ?? "",
|
|
369
|
-
startedAt: input.startedAt ?? this.now(),
|
|
370
|
-
correlationId: sanitizeCorrelationId(input.correlationId),
|
|
371
|
-
address: sanitizeAddress(input.address),
|
|
372
|
-
recipeName: sanitizeString(input.recipeName),
|
|
373
|
-
runtimeId: sanitizeString(input.runtimeId),
|
|
374
|
-
runtimeVersion: sanitizeString(input.runtimeVersion),
|
|
375
|
-
data: {
|
|
376
|
-
input: input.input,
|
|
377
|
-
status: "spawning",
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
return id;
|
|
381
|
-
}
|
|
382
|
-
catch (err) {
|
|
383
|
-
logger.warn("openSubagentSpan failed", { err: errMsg(err) });
|
|
384
|
-
return "";
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
/** Merge fields into an existing subagent (e.g., on _spawned, _delivery_target). Lazy-creates if missing. */
|
|
388
|
-
mergeSubagentSpan(input, context) {
|
|
389
|
-
try {
|
|
390
|
-
let open = this.openSubagents.get(input.childSessionKey);
|
|
391
|
-
if (!open) {
|
|
392
|
-
// Lazy-create: missed _spawning. Use what context we have.
|
|
393
|
-
const id = randomUUID();
|
|
394
|
-
open = {
|
|
395
|
-
id,
|
|
396
|
-
hostSessionKey: "",
|
|
397
|
-
hostRunId: "",
|
|
398
|
-
childSessionKey: input.childSessionKey,
|
|
399
|
-
parentSpanId: "",
|
|
400
|
-
startedAt: this.now(),
|
|
401
|
-
correlationId: sanitizeCorrelationId(context?.correlationId),
|
|
402
|
-
address: sanitizeAddress(context?.address),
|
|
403
|
-
recipeName: sanitizeString(context?.recipeName),
|
|
404
|
-
runtimeId: sanitizeString(context?.runtimeId),
|
|
405
|
-
runtimeVersion: sanitizeString(context?.runtimeVersion),
|
|
406
|
-
data: { status: input.status },
|
|
407
|
-
};
|
|
408
|
-
this.openSubagents.set(input.childSessionKey, open);
|
|
409
|
-
}
|
|
410
|
-
open.data.status = input.status;
|
|
411
|
-
if (input.patch) {
|
|
412
|
-
// shallow-merge any provided fields into the data root
|
|
413
|
-
for (const [k, v] of Object.entries(input.patch)) {
|
|
414
|
-
open.data[k] = v;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
catch (err) {
|
|
419
|
-
logger.warn("mergeSubagentSpan failed", { err: errMsg(err) });
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
finalizeSubagentSpan(input, context) {
|
|
423
|
-
try {
|
|
424
|
-
const open = this.openSubagents.get(input.childSessionKey);
|
|
425
|
-
if (!open) {
|
|
426
|
-
logger.warn("finalizeSubagentSpan: no open subagent", {
|
|
427
|
-
childSessionKey: input.childSessionKey,
|
|
428
|
-
});
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
this.openSubagents.delete(input.childSessionKey);
|
|
432
|
-
const endedAt = this.now();
|
|
433
|
-
const data = { ...open.data, status: input.status };
|
|
434
|
-
if (input.error !== undefined) {
|
|
435
|
-
data.output = { error: errMsg(input.error) };
|
|
436
|
-
}
|
|
437
|
-
else if (input.output !== undefined) {
|
|
438
|
-
data.output = input.output;
|
|
439
|
-
}
|
|
440
|
-
this.transport.enqueue({
|
|
441
|
-
id: open.id,
|
|
442
|
-
correlationId: sanitizeCorrelationId(context?.correlationId) ?? open.correlationId,
|
|
443
|
-
sessionKey: open.hostSessionKey,
|
|
444
|
-
runId: open.hostRunId,
|
|
445
|
-
childSessionKey: open.childSessionKey,
|
|
446
|
-
toolCallId: "",
|
|
447
|
-
parentSpanId: open.parentSpanId,
|
|
448
|
-
spanType: "subagent",
|
|
449
|
-
emissionSource: "hook",
|
|
450
|
-
timestamp: endedAt,
|
|
451
|
-
address: sanitizeAddress(context?.address) || open.address,
|
|
452
|
-
recipeName: sanitizeString(context?.recipeName) || open.recipeName,
|
|
453
|
-
runtimeId: sanitizeString(context?.runtimeId) || open.runtimeId,
|
|
454
|
-
runtimeVersion: sanitizeString(context?.runtimeVersion) || open.runtimeVersion,
|
|
455
|
-
startedAt: open.startedAt,
|
|
456
|
-
endedAt,
|
|
457
|
-
durationMs: Math.max(0, endedAt - open.startedAt),
|
|
458
|
-
model: "",
|
|
459
|
-
tokensIn: null,
|
|
460
|
-
tokensOut: null,
|
|
461
|
-
costUsd: null,
|
|
462
|
-
data,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
catch (err) {
|
|
466
|
-
logger.warn("finalizeSubagentSpan failed", { err: errMsg(err) });
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
// ─── Agent end — emit marker + sweep orphans for this (session, run) ────
|
|
470
|
-
captureAgentEnd(input) {
|
|
471
|
-
try {
|
|
472
|
-
// Sweep orphans for this session/run.
|
|
473
|
-
const prefix = `session:${input.sessionKey}:run:${input.runId}:`;
|
|
474
|
-
const keysToSweep = [];
|
|
475
|
-
for (const key of this.openSpans.keys()) {
|
|
476
|
-
if (key.startsWith(prefix))
|
|
477
|
-
keysToSweep.push(key);
|
|
478
|
-
}
|
|
479
|
-
for (const key of keysToSweep) {
|
|
480
|
-
const open = this.openSpans.get(key);
|
|
481
|
-
if (open) {
|
|
482
|
-
this.enqueueOrphan(open, "agent_end_without_close");
|
|
483
|
-
this.openSpans.delete(key);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
// Also sweep subagents hosted by this (session, run).
|
|
487
|
-
const subKeysToSweep = [];
|
|
488
|
-
for (const [k, sub] of this.openSubagents.entries()) {
|
|
489
|
-
if (sub.hostSessionKey === input.sessionKey && sub.hostRunId === input.runId) {
|
|
490
|
-
subKeysToSweep.push(k);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
for (const k of subKeysToSweep) {
|
|
494
|
-
const sub = this.openSubagents.get(k);
|
|
495
|
-
if (sub) {
|
|
496
|
-
this.enqueueSubagentOrphan(sub, "agent_end_without_close");
|
|
497
|
-
this.openSubagents.delete(k);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
const endedAt = this.now();
|
|
501
|
-
const data = {};
|
|
502
|
-
if (input.outcome !== undefined)
|
|
503
|
-
data.outcome = input.outcome;
|
|
504
|
-
if (input.error !== undefined)
|
|
505
|
-
data.error = errMsg(input.error);
|
|
506
|
-
this.transport.enqueue({
|
|
507
|
-
id: randomUUID(),
|
|
508
|
-
correlationId: sanitizeCorrelationId(input.correlationId),
|
|
509
|
-
sessionKey: input.sessionKey,
|
|
510
|
-
runId: input.runId,
|
|
511
|
-
childSessionKey: "",
|
|
512
|
-
toolCallId: "",
|
|
513
|
-
parentSpanId: "",
|
|
514
|
-
spanType: "agent_end",
|
|
515
|
-
emissionSource: "hook",
|
|
516
|
-
timestamp: endedAt,
|
|
517
|
-
address: sanitizeAddress(input.address),
|
|
518
|
-
recipeName: sanitizeString(input.recipeName),
|
|
519
|
-
runtimeId: sanitizeString(input.runtimeId),
|
|
520
|
-
runtimeVersion: sanitizeString(input.runtimeVersion),
|
|
521
|
-
startedAt: endedAt,
|
|
522
|
-
endedAt,
|
|
523
|
-
durationMs: 0,
|
|
524
|
-
model: "",
|
|
525
|
-
tokensIn: null,
|
|
526
|
-
tokensOut: null,
|
|
527
|
-
costUsd: null,
|
|
528
|
-
data,
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
catch (err) {
|
|
532
|
-
logger.warn("captureAgentEnd failed", { err: errMsg(err) });
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
// ─── Direct (manual) emission — for non-hook paths like AnthropicLlmDecision ──
|
|
536
|
-
captureLlmSpan(input) {
|
|
537
|
-
try {
|
|
538
|
-
const data = { input: input.input };
|
|
539
|
-
if (input.output !== undefined)
|
|
540
|
-
data.output = input.output;
|
|
541
|
-
if (input.error !== undefined)
|
|
542
|
-
data.error = errMsg(input.error);
|
|
543
|
-
this.transport.enqueue({
|
|
544
|
-
id: randomUUID(),
|
|
545
|
-
correlationId: sanitizeCorrelationId(input.correlationId),
|
|
546
|
-
sessionKey: input.sessionKey,
|
|
547
|
-
runId: input.runId,
|
|
548
|
-
childSessionKey: "",
|
|
549
|
-
toolCallId: "",
|
|
550
|
-
parentSpanId: input.parentSpanId ?? "",
|
|
551
|
-
spanType: "llm",
|
|
552
|
-
emissionSource: "direct",
|
|
553
|
-
timestamp: input.endedAt,
|
|
554
|
-
address: sanitizeAddress(input.address),
|
|
555
|
-
recipeName: sanitizeString(input.recipeName),
|
|
556
|
-
runtimeId: sanitizeString(input.runtimeId),
|
|
557
|
-
runtimeVersion: sanitizeString(input.runtimeVersion),
|
|
558
|
-
startedAt: input.startedAt,
|
|
559
|
-
endedAt: input.endedAt,
|
|
560
|
-
durationMs: Math.max(0, input.endedAt - input.startedAt),
|
|
561
|
-
model: sanitizeString(input.model),
|
|
562
|
-
tokensIn: sanitizeUint(input.tokensIn),
|
|
563
|
-
tokensOut: sanitizeUint(input.tokensOut),
|
|
564
|
-
costUsd: sanitizeCostUsd(input.costUsd),
|
|
565
|
-
data,
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
catch (err) {
|
|
569
|
-
logger.warn("captureLlmSpan failed", { err: errMsg(err) });
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
captureToolSpan(input) {
|
|
573
|
-
try {
|
|
574
|
-
const data = { tool_name: input.toolName };
|
|
575
|
-
if (input.input !== undefined)
|
|
576
|
-
data.input = input.input;
|
|
577
|
-
if (input.output !== undefined)
|
|
578
|
-
data.output = input.output;
|
|
579
|
-
if (input.error !== undefined)
|
|
580
|
-
data.error = errMsg(input.error);
|
|
581
|
-
this.transport.enqueue({
|
|
582
|
-
id: randomUUID(),
|
|
583
|
-
correlationId: sanitizeCorrelationId(input.correlationId),
|
|
584
|
-
sessionKey: input.sessionKey,
|
|
585
|
-
runId: input.runId,
|
|
586
|
-
childSessionKey: "",
|
|
587
|
-
toolCallId: input.toolCallId ?? "",
|
|
588
|
-
parentSpanId: input.parentSpanId ?? "",
|
|
589
|
-
spanType: "tool",
|
|
590
|
-
emissionSource: "direct",
|
|
591
|
-
timestamp: input.endedAt,
|
|
592
|
-
address: sanitizeAddress(input.address),
|
|
593
|
-
recipeName: sanitizeString(input.recipeName),
|
|
594
|
-
runtimeId: sanitizeString(input.runtimeId),
|
|
595
|
-
runtimeVersion: sanitizeString(input.runtimeVersion),
|
|
596
|
-
startedAt: input.startedAt,
|
|
597
|
-
endedAt: input.endedAt,
|
|
598
|
-
durationMs: Math.max(0, input.endedAt - input.startedAt),
|
|
599
|
-
model: "",
|
|
600
|
-
tokensIn: null,
|
|
601
|
-
tokensOut: null,
|
|
602
|
-
costUsd: null,
|
|
603
|
-
data,
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
catch (err) {
|
|
607
|
-
logger.warn("captureToolSpan failed", { err: errMsg(err) });
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
// ─── Maintenance ────────────────────────────────────────────────────────
|
|
611
|
-
sweepStale() {
|
|
612
|
-
try {
|
|
613
|
-
const cutoff = this.now() - this.staleTimeoutMs;
|
|
614
|
-
const keys = [];
|
|
615
|
-
for (const [k, open] of this.openSpans.entries()) {
|
|
616
|
-
if (open.startedAt < cutoff)
|
|
617
|
-
keys.push(k);
|
|
618
|
-
}
|
|
619
|
-
for (const k of keys) {
|
|
620
|
-
const open = this.openSpans.get(k);
|
|
621
|
-
if (open) {
|
|
622
|
-
this.enqueueOrphan(open, "stale_timeout");
|
|
623
|
-
this.openSpans.delete(k);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
const subKeys = [];
|
|
627
|
-
for (const [k, sub] of this.openSubagents.entries()) {
|
|
628
|
-
if (sub.startedAt < cutoff)
|
|
629
|
-
subKeys.push(k);
|
|
630
|
-
}
|
|
631
|
-
for (const k of subKeys) {
|
|
632
|
-
const sub = this.openSubagents.get(k);
|
|
633
|
-
if (sub) {
|
|
634
|
-
this.enqueueSubagentOrphan(sub, "stale_timeout");
|
|
635
|
-
this.openSubagents.delete(k);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
catch (err) {
|
|
640
|
-
logger.warn("sweepStale failed", { err: errMsg(err) });
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
async shutdown() {
|
|
644
|
-
if (this.sweepTimer) {
|
|
645
|
-
clearInterval(this.sweepTimer);
|
|
646
|
-
this.sweepTimer = null;
|
|
647
|
-
}
|
|
648
|
-
// Drain remaining open spans as orphans.
|
|
649
|
-
for (const [k, open] of this.openSpans.entries()) {
|
|
650
|
-
this.enqueueOrphan(open, "shutdown");
|
|
651
|
-
this.openSpans.delete(k);
|
|
652
|
-
}
|
|
653
|
-
for (const [k, sub] of this.openSubagents.entries()) {
|
|
654
|
-
this.enqueueSubagentOrphan(sub, "shutdown");
|
|
655
|
-
this.openSubagents.delete(k);
|
|
656
|
-
}
|
|
657
|
-
await this.transport.shutdown();
|
|
658
|
-
}
|
|
659
|
-
// ─── Internal: orphan enqueue helpers ───────────────────────────────────
|
|
660
|
-
enqueueOrphan(open, reason) {
|
|
661
|
-
const endedAt = this.now();
|
|
662
|
-
const data = { ...open.data, orphan_reason: reason };
|
|
663
|
-
const span = {
|
|
664
|
-
id: open.id,
|
|
665
|
-
correlationId: open.correlationId,
|
|
666
|
-
sessionKey: open.sessionKey,
|
|
667
|
-
runId: open.runId,
|
|
668
|
-
childSessionKey: "",
|
|
669
|
-
toolCallId: open.toolCallId,
|
|
670
|
-
parentSpanId: open.parentSpanId,
|
|
671
|
-
spanType: open.spanType,
|
|
672
|
-
emissionSource: open.emissionSource,
|
|
673
|
-
timestamp: endedAt,
|
|
674
|
-
address: open.address,
|
|
675
|
-
recipeName: open.recipeName,
|
|
676
|
-
runtimeId: open.runtimeId,
|
|
677
|
-
runtimeVersion: open.runtimeVersion,
|
|
678
|
-
startedAt: open.startedAt,
|
|
679
|
-
endedAt,
|
|
680
|
-
durationMs: Math.max(0, endedAt - open.startedAt),
|
|
681
|
-
model: "",
|
|
682
|
-
tokensIn: null,
|
|
683
|
-
tokensOut: null,
|
|
684
|
-
costUsd: null,
|
|
685
|
-
data,
|
|
686
|
-
};
|
|
687
|
-
this.transport.enqueue(span);
|
|
688
|
-
}
|
|
689
|
-
enqueueSubagentOrphan(open, reason) {
|
|
690
|
-
const endedAt = this.now();
|
|
691
|
-
const data = { ...open.data, orphan_reason: reason };
|
|
692
|
-
this.transport.enqueue({
|
|
693
|
-
id: open.id,
|
|
694
|
-
correlationId: open.correlationId,
|
|
695
|
-
sessionKey: open.hostSessionKey,
|
|
696
|
-
runId: open.hostRunId,
|
|
697
|
-
childSessionKey: open.childSessionKey,
|
|
698
|
-
toolCallId: "",
|
|
699
|
-
parentSpanId: open.parentSpanId,
|
|
700
|
-
spanType: "subagent",
|
|
701
|
-
emissionSource: "hook",
|
|
702
|
-
timestamp: endedAt,
|
|
703
|
-
address: open.address,
|
|
704
|
-
recipeName: open.recipeName,
|
|
705
|
-
runtimeId: open.runtimeId,
|
|
706
|
-
runtimeVersion: open.runtimeVersion,
|
|
707
|
-
startedAt: open.startedAt,
|
|
708
|
-
endedAt,
|
|
709
|
-
durationMs: Math.max(0, endedAt - open.startedAt),
|
|
710
|
-
model: "",
|
|
711
|
-
tokensIn: null,
|
|
712
|
-
tokensOut: null,
|
|
713
|
-
costUsd: null,
|
|
714
|
-
data,
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
function errMsg(err) {
|
|
719
|
-
return err instanceof Error ? err.message : String(err);
|
|
720
|
-
}
|
|
721
|
-
//# sourceMappingURL=span-tracker.js.map
|