@tangle-network/agent-app 0.10.0 → 0.11.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/README.md +10 -0
- package/dist/agent-activity-C8ZG0F0M.d.ts +43 -0
- package/dist/chunk-5RT6KY4G.js +190 -0
- package/dist/chunk-5RT6KY4G.js.map +1 -0
- package/dist/chunk-AFDROJ64.js +218 -0
- package/dist/chunk-AFDROJ64.js.map +1 -0
- package/dist/{chunk-UIWB2F6N.js → chunk-UDXMR3AD.js} +80 -24
- package/dist/chunk-UDXMR3AD.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +31 -1
- package/dist/missions/index.d.ts +38 -5
- package/dist/missions/index.js +3 -1
- package/dist/trace/index.d.ts +137 -1
- package/dist/trace/index.js +24 -138
- package/dist/trace/index.js.map +1 -1
- package/dist/web-react/index.d.ts +79 -1
- package/dist/web-react/index.js +417 -116
- package/dist/web-react/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-UIWB2F6N.js.map +0 -1
package/README.md
CHANGED
|
@@ -118,12 +118,22 @@ Each is an independent entry point — import only what you use.
|
|
|
118
118
|
| [`/billing`](src/billing) | `createWorkspaceKeyManager` — mint / rotate / roll over / report usage on per-workspace, budget-capped model keys. Seams for provisioner, store, and crypto. |
|
|
119
119
|
| [`/delegation`](src/delegation) | `buildDelegationMcpServer` — the agent-runtime driven-loop MCP (`delegate_research`, `delegate_code`, `delegation_status`) for multi-step work that runs to completion in its own sandbox. Opt-in. |
|
|
120
120
|
| [`/crypto`](src/crypto) | AES-GCM field encryption: `encryptAesGcm`, `decryptAesGcm`, `createFieldCrypto`. Key supplied by the caller. |
|
|
121
|
+
| [`/missions`](src/missions) | Durable multi-step mission orchestration over a `MissionStorePort` seam: guarded status/step machine, idempotent plan engine with budget/approval gates, `:::mission` parser, the client-safe live-event reducer, and the canonical `StepAgentActivity` per-step delegated-run lane. |
|
|
122
|
+
| [`/trace`](src/trace) | Flow observability: `buildFlowTrace` + ASCII `renderWaterfall`/`renderHistogram`; the mission trace bridge (`createMissionTraceContext`, `childSpanContext`, `traceEnv`) whose ids/env agent-runtime's delegation MCP inherits; and delegation→FlowSpan converters (`delegationActivityToFlowSpans`, `loopTraceEventsToFlowSpans`, `composeMissionFlowTrace`). |
|
|
123
|
+
| [`/web-react`](src/web-react) | Shared React components: `ModelPicker`, `EffortPicker`, `ChatMessages`, `RunDrillIn`, plus the observability surfaces — `MissionActivityLane`, `AgentActivityPanel`, `FlowWaterfall`. React is an optional peer; not re-exported from the root entry. |
|
|
121
124
|
| [`/web`](src/web) | Request-boundary utilities: `parseJsonObjectBody`, `requireString`, `extractRequestContext`, `checkRateLimit`, `addSecurityHeaders`. |
|
|
122
125
|
| [`/stream`](src/stream) | SSE normalization and turn identity: `normalizeToolEvent`, `resolveChatTurn`, `encodeEvent`, message-part merging. |
|
|
123
126
|
| [`/redact`](src/redact) | `redactForIngestion` — PII redaction before content leaves the boundary. |
|
|
124
127
|
|
|
125
128
|
The root entry (`@tangle-network/agent-app`) re-exports every module, but importing the subpath keeps your bundle to what you use.
|
|
126
129
|
|
|
130
|
+
### Missions: id shape and product columns
|
|
131
|
+
|
|
132
|
+
Two `createMissionService` seams adopters hit on day one:
|
|
133
|
+
|
|
134
|
+
- **`generateId`** (on `MissionServiceOptions`) defaults to `crypto.randomUUID()` — a 36-char dashed UUID. If your mission table has an existing id shape (e.g. 32-hex to match D1 row defaults), inject your own generator; the service stamps it verbatim on the inserted record.
|
|
135
|
+
- **`CreateMissionInput.extras`** carries opaque product-column values (a `workflowId` FK, a source-turn pointer) verbatim to `MissionStorePort.insert(record, extras)`, so creation is a single write — no post-insert stamp. The service never reads them.
|
|
136
|
+
|
|
127
137
|
## Compatibility
|
|
128
138
|
|
|
129
139
|
- **ESM only.** Ships `import` + `types` conditions per subpath.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical shape for the per-step agent-activity lane — the delegated runs
|
|
3
|
+
* (delegation MCP registry entries) a mission step spawned, journaled onto the
|
|
4
|
+
* step so a live UI can render "what the agent is actually doing" under the
|
|
5
|
+
* step row.
|
|
6
|
+
*
|
|
7
|
+
* CLIENT-SAFE, like everything in `./events`: pure data + a validator. The
|
|
8
|
+
* server attaches `StepAgentActivity[]` when a step settles (and live via
|
|
9
|
+
* `step.updated`); the client re-validates with {@link stepAgentActivity}
|
|
10
|
+
* because the value rides loader/JSON boundaries as untyped metadata.
|
|
11
|
+
*/
|
|
12
|
+
interface StepAgentActivity {
|
|
13
|
+
taskId: string;
|
|
14
|
+
/** Delegation profile: coder | researcher | ui-auditor | product-defined. */
|
|
15
|
+
tool: string;
|
|
16
|
+
status: string;
|
|
17
|
+
/** Title-ish excerpt of the delegation's args (goal / question / audit root). */
|
|
18
|
+
detail: string;
|
|
19
|
+
costUsd?: number;
|
|
20
|
+
durationMs?: number;
|
|
21
|
+
/** ISO timestamp the delegation started. */
|
|
22
|
+
startedAt: string;
|
|
23
|
+
/** Live loop progress (DelegationProgress.iteration) while the run is in flight. */
|
|
24
|
+
iteration?: number;
|
|
25
|
+
/** Live loop progress (DelegationProgress.phase) while the run is in flight. */
|
|
26
|
+
phase?: string;
|
|
27
|
+
/** 32-hex trace id when the delegation joined a mission trace (see `/trace`). */
|
|
28
|
+
traceId?: string;
|
|
29
|
+
/** 16-hex span id of the delegation's span inside that trace. */
|
|
30
|
+
spanId?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Step state extended with the activity lane a loader/seed route attaches. */
|
|
33
|
+
type WithAgentActivity<Step> = Step & {
|
|
34
|
+
agentActivity?: StepAgentActivity[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Re-validate an `agentActivity` lane that crossed a JSON boundary. Entries
|
|
38
|
+
* missing any required field are dropped (a torn row must not crash the lane);
|
|
39
|
+
* optional fields are kept only when well-typed.
|
|
40
|
+
*/
|
|
41
|
+
declare function stepAgentActivity(step: object): StepAgentActivity[];
|
|
42
|
+
|
|
43
|
+
export { type StepAgentActivity as S, type WithAgentActivity as W, stepAgentActivity as s };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/trace/mission-trace.ts
|
|
2
|
+
function createMissionTraceContext(missionId) {
|
|
3
|
+
if (missionId !== void 0 && missionId !== "") {
|
|
4
|
+
return {
|
|
5
|
+
traceId: hex64(fnv1a64(`mission-trace:${missionId}`)) + hex64(fnv1a64(`mission-trace:2:${missionId}`)),
|
|
6
|
+
rootSpanId: hex64(fnv1a64(`mission-root-span:${missionId}`))
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
return { traceId: randomHex(16), rootSpanId: randomHex(8) };
|
|
10
|
+
}
|
|
11
|
+
function childSpanContext(parent, seed) {
|
|
12
|
+
const parentSpanId = "rootSpanId" in parent ? parent.rootSpanId : parent.spanId;
|
|
13
|
+
const spanId = seed !== void 0 && seed !== "" ? hex64(fnv1a64(`span:${parent.traceId}:${parentSpanId}:${seed}`)) : randomHex(8);
|
|
14
|
+
return { traceId: parent.traceId, spanId, parentSpanId };
|
|
15
|
+
}
|
|
16
|
+
function traceEnv(ctx) {
|
|
17
|
+
return {
|
|
18
|
+
TRACE_ID: ctx.traceId,
|
|
19
|
+
PARENT_SPAN_ID: "rootSpanId" in ctx ? ctx.rootSpanId : ctx.spanId
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
var FNV_OFFSET = 0xcbf29ce484222325n;
|
|
23
|
+
var FNV_PRIME = 0x100000001b3n;
|
|
24
|
+
var MASK_64 = 0xffffffffffffffffn;
|
|
25
|
+
function fnv1a64(input) {
|
|
26
|
+
let hash = FNV_OFFSET;
|
|
27
|
+
for (let i = 0; i < input.length; i++) {
|
|
28
|
+
hash ^= BigInt(input.charCodeAt(i));
|
|
29
|
+
hash = hash * FNV_PRIME & MASK_64;
|
|
30
|
+
}
|
|
31
|
+
return hash;
|
|
32
|
+
}
|
|
33
|
+
function hex64(value) {
|
|
34
|
+
return value.toString(16).padStart(16, "0");
|
|
35
|
+
}
|
|
36
|
+
function randomHex(byteLength) {
|
|
37
|
+
const bytes = new Uint8Array(byteLength);
|
|
38
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
39
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/trace/index.ts
|
|
43
|
+
function timedEventsFromLines(lines) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(line);
|
|
48
|
+
if (typeof parsed._t === "number") out.push({ t: parsed._t, event: parsed });
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out.sort((a, b) => a.t - b.t);
|
|
53
|
+
}
|
|
54
|
+
function innerOf(e) {
|
|
55
|
+
return (e.kind === "event" ? e.event : e) ?? {};
|
|
56
|
+
}
|
|
57
|
+
function buildFlowTrace(events, opts) {
|
|
58
|
+
const spans = [];
|
|
59
|
+
let promptTokens = 0;
|
|
60
|
+
let completionTokens = 0;
|
|
61
|
+
let toolCalls = 0;
|
|
62
|
+
const first = events[0]?.t ?? 0;
|
|
63
|
+
if (first > 0) {
|
|
64
|
+
spans.push({ kind: "pipeline", name: "dispatch \u2192 first event", startMs: 0, endMs: first });
|
|
65
|
+
}
|
|
66
|
+
let segStart = null;
|
|
67
|
+
let segEnd = 0;
|
|
68
|
+
let segKinds = /* @__PURE__ */ new Set();
|
|
69
|
+
let lastDeltaT = first;
|
|
70
|
+
const openCalls = /* @__PURE__ */ new Map();
|
|
71
|
+
const closeSegment = () => {
|
|
72
|
+
if (segStart !== null) {
|
|
73
|
+
spans.push({
|
|
74
|
+
kind: "model",
|
|
75
|
+
name: segKinds.has("reasoning") ? "model turn (reasoning + text)" : "model turn",
|
|
76
|
+
startMs: segStart,
|
|
77
|
+
endMs: segEnd,
|
|
78
|
+
approx: true
|
|
79
|
+
});
|
|
80
|
+
segStart = null;
|
|
81
|
+
segKinds = /* @__PURE__ */ new Set();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
for (const { t, event } of events) {
|
|
85
|
+
const inner = innerOf(event);
|
|
86
|
+
const type = String(event.kind === "tool_result" ? "tool_result" : inner.type ?? "");
|
|
87
|
+
if (type === "text" || type === "reasoning") {
|
|
88
|
+
if (segStart === null) segStart = t;
|
|
89
|
+
segEnd = t;
|
|
90
|
+
segKinds.add(type);
|
|
91
|
+
lastDeltaT = t;
|
|
92
|
+
} else if (type === "tool_call") {
|
|
93
|
+
closeSegment();
|
|
94
|
+
toolCalls++;
|
|
95
|
+
const call = inner.call ?? inner;
|
|
96
|
+
const id = String(call.toolCallId ?? `call_${toolCalls}`);
|
|
97
|
+
openCalls.set(id, { name: String(call.toolName ?? "tool"), emitT: t, lastDeltaT });
|
|
98
|
+
} else if (type === "tool_result") {
|
|
99
|
+
const id = String(event.toolCallId ?? inner.toolCallId ?? "");
|
|
100
|
+
const open = openCalls.get(id);
|
|
101
|
+
if (open) {
|
|
102
|
+
spans.push({
|
|
103
|
+
kind: "tool",
|
|
104
|
+
name: open.name,
|
|
105
|
+
// Execution happens between the end of the model turn that emitted
|
|
106
|
+
// the call and the result landing in the buffer.
|
|
107
|
+
startMs: open.lastDeltaT,
|
|
108
|
+
endMs: t,
|
|
109
|
+
approx: true,
|
|
110
|
+
meta: { ok: (event.outcome ?? inner.outcome)?.ok }
|
|
111
|
+
});
|
|
112
|
+
openCalls.delete(id);
|
|
113
|
+
}
|
|
114
|
+
} else if (type === "usage") {
|
|
115
|
+
const u = inner.usage ?? {};
|
|
116
|
+
promptTokens += u.promptTokens ?? 0;
|
|
117
|
+
completionTokens += u.completionTokens ?? 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
closeSegment();
|
|
121
|
+
const totalMs = events.length ? events[events.length - 1].t : 0;
|
|
122
|
+
const trace = { spans, totalMs, promptTokens, completionTokens, toolCalls };
|
|
123
|
+
const p = opts?.pricing;
|
|
124
|
+
if (p && (p.prompt != null || p.completion != null)) {
|
|
125
|
+
trace.costUsd = promptTokens * Number(p.prompt ?? 0) + completionTokens * Number(p.completion ?? 0);
|
|
126
|
+
}
|
|
127
|
+
return trace;
|
|
128
|
+
}
|
|
129
|
+
var fmtS = (ms) => `${(ms / 1e3).toFixed(1)}s`;
|
|
130
|
+
function renderWaterfall(trace, opts) {
|
|
131
|
+
const width = opts?.width ?? 40;
|
|
132
|
+
const scale = trace.totalMs > 0 ? width / trace.totalMs : 0;
|
|
133
|
+
const lines = [];
|
|
134
|
+
const spans = [...trace.spans].sort((a, b) => a.startMs - b.startMs);
|
|
135
|
+
for (let i = 0; i < spans.length; i++) {
|
|
136
|
+
const s = spans[i];
|
|
137
|
+
const offset = Math.round(s.startMs * scale);
|
|
138
|
+
const len = Math.max(1, Math.round((s.endMs - s.startMs) * scale));
|
|
139
|
+
const bar = " ".repeat(offset) + (s.kind === "tool" ? "\u2593" : s.kind === "pipeline" ? "\u2591" : "\u2588").repeat(len);
|
|
140
|
+
const branch = i === spans.length - 1 ? "\u2514\u2500" : "\u251C\u2500";
|
|
141
|
+
const dur = `${fmtS(s.endMs - s.startMs)}${s.approx ? "~" : ""}`;
|
|
142
|
+
lines.push(`${fmtS(s.startMs).padStart(7)} ${branch} ${bar.padEnd(width + 2)} ${s.name} (${dur})`);
|
|
143
|
+
}
|
|
144
|
+
const cost = trace.costUsd != null ? ` $${trace.costUsd.toFixed(trace.costUsd < 0.01 ? 6 : 4)}` : "";
|
|
145
|
+
lines.push(
|
|
146
|
+
`${fmtS(trace.totalMs).padStart(7)} \u2500\u2500 total \xB7 ${trace.promptTokens}p + ${trace.completionTokens}c tok \xB7 ${trace.toolCalls} tool calls${cost}`
|
|
147
|
+
);
|
|
148
|
+
return lines.join("\n");
|
|
149
|
+
}
|
|
150
|
+
function summarize(values) {
|
|
151
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
152
|
+
const q = (p) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))] ?? 0;
|
|
153
|
+
return { n: sorted.length, min: sorted[0] ?? 0, p50: q(0.5), p90: q(0.9), max: sorted[sorted.length - 1] ?? 0 };
|
|
154
|
+
}
|
|
155
|
+
function renderHistogram(values, opts) {
|
|
156
|
+
if (!values.length) return "(no samples)";
|
|
157
|
+
const buckets = opts?.buckets ?? 6;
|
|
158
|
+
const width = opts?.width ?? 24;
|
|
159
|
+
const fmt = opts?.format ?? ((v) => `${Math.round(v)}${opts?.unit ?? ""}`);
|
|
160
|
+
const s = summarize(values);
|
|
161
|
+
const lo = s.min;
|
|
162
|
+
const hi = s.max === s.min ? s.min + 1 : s.max;
|
|
163
|
+
const counts = new Array(buckets).fill(0);
|
|
164
|
+
for (const v of values) {
|
|
165
|
+
counts[Math.min(buckets - 1, Math.floor((v - lo) / (hi - lo) * buckets))]++;
|
|
166
|
+
}
|
|
167
|
+
const maxCount = Math.max(...counts);
|
|
168
|
+
const lines = [
|
|
169
|
+
`n=${s.n} min=${fmt(s.min)} p50=${fmt(s.p50)} p90=${fmt(s.p90)} max=${fmt(s.max)}`
|
|
170
|
+
];
|
|
171
|
+
for (let i = 0; i < buckets; i++) {
|
|
172
|
+
const a = lo + (hi - lo) * i / buckets;
|
|
173
|
+
const b = lo + (hi - lo) * (i + 1) / buckets;
|
|
174
|
+
const bar = "\u2588".repeat(Math.max(counts[i] > 0 ? 1 : 0, Math.round(counts[i] / maxCount * width)));
|
|
175
|
+
lines.push(`${fmt(a).padStart(8)}-${fmt(b).padEnd(8)} ${bar} ${counts[i]}`);
|
|
176
|
+
}
|
|
177
|
+
return lines.join("\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
createMissionTraceContext,
|
|
182
|
+
childSpanContext,
|
|
183
|
+
traceEnv,
|
|
184
|
+
timedEventsFromLines,
|
|
185
|
+
buildFlowTrace,
|
|
186
|
+
renderWaterfall,
|
|
187
|
+
summarize,
|
|
188
|
+
renderHistogram
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=chunk-5RT6KY4G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/trace/mission-trace.ts","../src/trace/index.ts"],"sourcesContent":["/**\n * Mission trace context — mint + thread the trace ids that join a mission's\n * step attempts and its delegated agent runs into ONE trace tree.\n *\n * ID formats are byte-compatible with agent-runtime's trace propagation\n * (`readTraceContextFromEnv` / OTLP export): 32 lowercase hex chars for a\n * trace id (16 bytes), 16 for a span id (8 bytes). The env pair from\n * {@link traceEnv} is exactly what agent-runtime's MCP subprocess reads at\n * startup (`TRACE_ID` + `PARENT_SPAN_ID`), so a delegation dispatched with it\n * parents its loop→round→iteration spans under the mission's step span.\n *\n * Pure functions, no deps. Ids are DETERMINISTIC when a key is supplied\n * (missionId / step-attempt seed) so a crashed driver re-mints the identical\n * context on re-dispatch and the re-run joins the same trace instead of\n * forking a new one; without a key they are random.\n */\n\nexport interface MissionTraceContext {\n /** 32-hex trace id shared by every span in the mission's tree. */\n traceId: string\n /** 16-hex span id of the mission root — the parent of every step span. */\n rootSpanId: string\n}\n\nexport interface StepSpanContext {\n traceId: string\n /** 16-hex span id of this step attempt (or any nested unit of work). */\n spanId: string\n /** The span this one nests under. */\n parentSpanId: string\n}\n\n/**\n * Mint a mission's trace context. With `missionId` the ids are a pure\n * function of it; omitted, both ids are random.\n */\nexport function createMissionTraceContext(missionId?: string): MissionTraceContext {\n if (missionId !== undefined && missionId !== '') {\n return {\n traceId: hex64(fnv1a64(`mission-trace:${missionId}`)) + hex64(fnv1a64(`mission-trace:2:${missionId}`)),\n rootSpanId: hex64(fnv1a64(`mission-root-span:${missionId}`)),\n }\n }\n return { traceId: randomHex(16), rootSpanId: randomHex(8) }\n}\n\n/**\n * Derive a child span context under `parent` — one per step attempt (seed\n * e.g. `\"${stepId}#${attempt}\"`), or nested under another step span. With a\n * seed the span id is deterministic for the same parent + seed; omitted, it\n * is random.\n */\nexport function childSpanContext(\n parent: MissionTraceContext | StepSpanContext,\n seed?: string,\n): StepSpanContext {\n const parentSpanId = 'rootSpanId' in parent ? parent.rootSpanId : parent.spanId\n const spanId =\n seed !== undefined && seed !== ''\n ? hex64(fnv1a64(`span:${parent.traceId}:${parentSpanId}:${seed}`))\n : randomHex(8)\n return { traceId: parent.traceId, spanId, parentSpanId }\n}\n\n/**\n * The env pair a delegation subprocess inherits — agent-runtime's\n * `readTraceContextFromEnv` reads exactly these names. `PARENT_SPAN_ID` is\n * the span the dispatched work nests under: the root for a mission context,\n * the step-attempt span for a step context.\n */\nexport function traceEnv(ctx: MissionTraceContext | StepSpanContext): {\n TRACE_ID: string\n PARENT_SPAN_ID: string\n} {\n return {\n TRACE_ID: ctx.traceId,\n PARENT_SPAN_ID: 'rootSpanId' in ctx ? ctx.rootSpanId : ctx.spanId,\n }\n}\n\n// FNV-1a 64-bit over UTF-16 code units — a stable, dependency-free digest for\n// deterministic ids. Not cryptographic; trace ids only need uniqueness within\n// a tenant's missions, not unforgeability.\nconst FNV_OFFSET = 0xcbf29ce484222325n\nconst FNV_PRIME = 0x100000001b3n\nconst MASK_64 = 0xffffffffffffffffn\n\nfunction fnv1a64(input: string): bigint {\n let hash = FNV_OFFSET\n for (let i = 0; i < input.length; i++) {\n hash ^= BigInt(input.charCodeAt(i))\n hash = (hash * FNV_PRIME) & MASK_64\n }\n return hash\n}\n\nfunction hex64(value: bigint): string {\n return value.toString(16).padStart(16, '0')\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = new Uint8Array(byteLength)\n globalThis.crypto.getRandomValues(bytes)\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n","/**\n * `@tangle-network/agent-app/trace` — flow observability for agent turns.\n *\n * The turn buffer stamps `_t` (ms since turn start) on every event, so any\n * live stream OR any historical turn replayed from a TurnEventStore can be\n * reconstructed into a span trace: pipeline overhead, model segments (with\n * thinking TTFT), tool executions, token usage, and cost. Renderers turn\n * traces and multi-run samples into ASCII waterfalls and histograms — the\n * default artifact for \"how did this run actually behave\" questions across\n * evals, hill-climbs, and production debugging.\n *\n * Span boundaries derived from a buffered stream are quantized by the\n * pump's flush window and the reader's poll cadence (~100–400ms); spans\n * carry `approx: true` to keep reports honest about that.\n */\n\nexport * from './mission-trace'\nexport * from './mission-flow'\n\nexport interface TimedEvent {\n /** ms since turn start (`_t` stamped by pumpBufferedTurn). */\n t: number\n event: Record<string, unknown>\n}\n\nexport interface FlowSpan {\n kind: 'pipeline' | 'model' | 'tool'\n name: string\n startMs: number\n endMs: number\n approx?: boolean\n meta?: Record<string, unknown>\n}\n\nexport interface FlowTrace {\n spans: FlowSpan[]\n totalMs: number\n promptTokens: number\n completionTokens: number\n /** Computed when per-token pricing is supplied. */\n costUsd?: number\n toolCalls: number\n}\n\n/** Parse stored turn-event lines (JSON strings with `_t`) into TimedEvents. */\nexport function timedEventsFromLines(lines: string[]): TimedEvent[] {\n const out: TimedEvent[] = []\n for (const line of lines) {\n try {\n const parsed = JSON.parse(line) as Record<string, unknown>\n if (typeof parsed._t === 'number') out.push({ t: parsed._t, event: parsed })\n } catch {\n /* skip torn lines */\n }\n }\n return out.sort((a, b) => a.t - b.t)\n}\n\nfunction innerOf(e: Record<string, unknown>): Record<string, unknown> {\n return (e.kind === 'event' ? (e.event as Record<string, unknown>) : e) ?? {}\n}\n\n/**\n * Derive a span trace from timestamped turn events. Model segments are runs\n * of text/reasoning deltas; a tool span opens at the last delta before its\n * tool_call emission and closes at the matching tool_result.\n */\nexport function buildFlowTrace(\n events: TimedEvent[],\n opts?: { pricing?: { prompt?: string | number; completion?: string | number } },\n): FlowTrace {\n const spans: FlowSpan[] = []\n let promptTokens = 0\n let completionTokens = 0\n let toolCalls = 0\n\n const first = events[0]?.t ?? 0\n if (first > 0) {\n spans.push({ kind: 'pipeline', name: 'dispatch → first event', startMs: 0, endMs: first })\n }\n\n let segStart: number | null = null\n let segEnd = 0\n let segKinds = new Set<string>()\n let lastDeltaT = first\n const openCalls = new Map<string, { name: string; emitT: number; lastDeltaT: number }>()\n\n const closeSegment = () => {\n if (segStart !== null) {\n spans.push({\n kind: 'model',\n name: segKinds.has('reasoning') ? 'model turn (reasoning + text)' : 'model turn',\n startMs: segStart,\n endMs: segEnd,\n approx: true,\n })\n segStart = null\n segKinds = new Set()\n }\n }\n\n for (const { t, event } of events) {\n const inner = innerOf(event)\n const type = String(event.kind === 'tool_result' ? 'tool_result' : (inner.type ?? ''))\n\n if (type === 'text' || type === 'reasoning') {\n if (segStart === null) segStart = t\n segEnd = t\n segKinds.add(type)\n lastDeltaT = t\n } else if (type === 'tool_call') {\n closeSegment()\n toolCalls++\n const call = (inner.call ?? inner) as Record<string, unknown>\n const id = String(call.toolCallId ?? `call_${toolCalls}`)\n openCalls.set(id, { name: String(call.toolName ?? 'tool'), emitT: t, lastDeltaT })\n } else if (type === 'tool_result') {\n const id = String(event.toolCallId ?? inner.toolCallId ?? '')\n const open = openCalls.get(id)\n if (open) {\n spans.push({\n kind: 'tool',\n name: open.name,\n // Execution happens between the end of the model turn that emitted\n // the call and the result landing in the buffer.\n startMs: open.lastDeltaT,\n endMs: t,\n approx: true,\n meta: { ok: ((event.outcome ?? inner.outcome) as { ok?: boolean } | undefined)?.ok },\n })\n openCalls.delete(id)\n }\n } else if (type === 'usage') {\n const u = (inner.usage ?? {}) as { promptTokens?: number; completionTokens?: number }\n promptTokens += u.promptTokens ?? 0\n completionTokens += u.completionTokens ?? 0\n }\n }\n closeSegment()\n\n const totalMs = events.length ? events[events.length - 1]!.t : 0\n const trace: FlowTrace = { spans, totalMs, promptTokens, completionTokens, toolCalls }\n const p = opts?.pricing\n if (p && (p.prompt != null || p.completion != null)) {\n trace.costUsd = promptTokens * Number(p.prompt ?? 0) + completionTokens * Number(p.completion ?? 0)\n }\n return trace\n}\n\nconst fmtS = (ms: number) => `${(ms / 1000).toFixed(1)}s`\n\n/** ASCII waterfall cascade — the default artifact for explaining a flow. */\nexport function renderWaterfall(trace: FlowTrace, opts?: { width?: number }): string {\n const width = opts?.width ?? 40\n const scale = trace.totalMs > 0 ? width / trace.totalMs : 0\n const lines: string[] = []\n const spans = [...trace.spans].sort((a, b) => a.startMs - b.startMs)\n for (let i = 0; i < spans.length; i++) {\n const s = spans[i]!\n const offset = Math.round(s.startMs * scale)\n const len = Math.max(1, Math.round((s.endMs - s.startMs) * scale))\n const bar = ' '.repeat(offset) + (s.kind === 'tool' ? '▓' : s.kind === 'pipeline' ? '░' : '█').repeat(len)\n const branch = i === spans.length - 1 ? '└─' : '├─'\n const dur = `${fmtS(s.endMs - s.startMs)}${s.approx ? '~' : ''}`\n lines.push(`${fmtS(s.startMs).padStart(7)} ${branch} ${bar.padEnd(width + 2)} ${s.name} (${dur})`)\n }\n const cost = trace.costUsd != null ? ` $${trace.costUsd.toFixed(trace.costUsd < 0.01 ? 6 : 4)}` : ''\n lines.push(\n `${fmtS(trace.totalMs).padStart(7)} ── total · ${trace.promptTokens}p + ${trace.completionTokens}c tok · ${trace.toolCalls} tool calls${cost}`,\n )\n return lines.join('\\n')\n}\n\nexport interface DistributionSummary {\n n: number\n min: number\n p50: number\n p90: number\n max: number\n}\n\nexport function summarize(values: number[]): DistributionSummary {\n const sorted = [...values].sort((a, b) => a - b)\n const q = (p: number) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))] ?? 0\n return { n: sorted.length, min: sorted[0] ?? 0, p50: q(0.5), p90: q(0.9), max: sorted[sorted.length - 1] ?? 0 }\n}\n\n/** ASCII histogram for multi-run samples (eval latencies, costs, scores). */\nexport function renderHistogram(\n values: number[],\n opts?: { buckets?: number; width?: number; unit?: string; format?: (v: number) => string },\n): string {\n if (!values.length) return '(no samples)'\n const buckets = opts?.buckets ?? 6\n const width = opts?.width ?? 24\n const fmt = opts?.format ?? ((v: number) => `${Math.round(v)}${opts?.unit ?? ''}`)\n const s = summarize(values)\n const lo = s.min\n const hi = s.max === s.min ? s.min + 1 : s.max\n const counts = new Array<number>(buckets).fill(0)\n for (const v of values) {\n counts[Math.min(buckets - 1, Math.floor(((v - lo) / (hi - lo)) * buckets))]!++\n }\n const maxCount = Math.max(...counts)\n const lines = [\n `n=${s.n} min=${fmt(s.min)} p50=${fmt(s.p50)} p90=${fmt(s.p90)} max=${fmt(s.max)}`,\n ]\n for (let i = 0; i < buckets; i++) {\n const a = lo + ((hi - lo) * i) / buckets\n const b = lo + ((hi - lo) * (i + 1)) / buckets\n const bar = '█'.repeat(Math.max(counts[i]! > 0 ? 1 : 0, Math.round((counts[i]! / maxCount) * width)))\n lines.push(`${fmt(a).padStart(8)}-${fmt(b).padEnd(8)} ${bar} ${counts[i]}`)\n }\n return lines.join('\\n')\n}\n"],"mappings":";AAoCO,SAAS,0BAA0B,WAAyC;AACjF,MAAI,cAAc,UAAa,cAAc,IAAI;AAC/C,WAAO;AAAA,MACL,SAAS,MAAM,QAAQ,iBAAiB,SAAS,EAAE,CAAC,IAAI,MAAM,QAAQ,mBAAmB,SAAS,EAAE,CAAC;AAAA,MACrG,YAAY,MAAM,QAAQ,qBAAqB,SAAS,EAAE,CAAC;AAAA,IAC7D;AAAA,EACF;AACA,SAAO,EAAE,SAAS,UAAU,EAAE,GAAG,YAAY,UAAU,CAAC,EAAE;AAC5D;AAQO,SAAS,iBACd,QACA,MACiB;AACjB,QAAM,eAAe,gBAAgB,SAAS,OAAO,aAAa,OAAO;AACzE,QAAM,SACJ,SAAS,UAAa,SAAS,KAC3B,MAAM,QAAQ,QAAQ,OAAO,OAAO,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC,IAC/D,UAAU,CAAC;AACjB,SAAO,EAAE,SAAS,OAAO,SAAS,QAAQ,aAAa;AACzD;AAQO,SAAS,SAAS,KAGvB;AACA,SAAO;AAAA,IACL,UAAU,IAAI;AAAA,IACd,gBAAgB,gBAAgB,MAAM,IAAI,aAAa,IAAI;AAAA,EAC7D;AACF;AAKA,IAAM,aAAa;AACnB,IAAM,YAAY;AAClB,IAAM,UAAU;AAEhB,SAAS,QAAQ,OAAuB;AACtC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAQ,OAAO,MAAM,WAAW,CAAC,CAAC;AAClC,WAAQ,OAAO,YAAa;AAAA,EAC9B;AACA,SAAO;AACT;AAEA,SAAS,MAAM,OAAuB;AACpC,SAAO,MAAM,SAAS,EAAE,EAAE,SAAS,IAAI,GAAG;AAC5C;AAEA,SAAS,UAAU,YAA4B;AAC7C,QAAM,QAAQ,IAAI,WAAW,UAAU;AACvC,aAAW,OAAO,gBAAgB,KAAK;AACvC,SAAO,MAAM,KAAK,KAAK,EACpB,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AACZ;;;AC7DO,SAAS,qBAAqB,OAA+B;AAClE,QAAM,MAAoB,CAAC;AAC3B,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,UAAI,OAAO,OAAO,OAAO,SAAU,KAAI,KAAK,EAAE,GAAG,OAAO,IAAI,OAAO,OAAO,CAAC;AAAA,IAC7E,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AACrC;AAEA,SAAS,QAAQ,GAAqD;AACpE,UAAQ,EAAE,SAAS,UAAW,EAAE,QAAoC,MAAM,CAAC;AAC7E;AAOO,SAAS,eACd,QACA,MACW;AACX,QAAM,QAAoB,CAAC;AAC3B,MAAI,eAAe;AACnB,MAAI,mBAAmB;AACvB,MAAI,YAAY;AAEhB,QAAM,QAAQ,OAAO,CAAC,GAAG,KAAK;AAC9B,MAAI,QAAQ,GAAG;AACb,UAAM,KAAK,EAAE,MAAM,YAAY,MAAM,+BAA0B,SAAS,GAAG,OAAO,MAAM,CAAC;AAAA,EAC3F;AAEA,MAAI,WAA0B;AAC9B,MAAI,SAAS;AACb,MAAI,WAAW,oBAAI,IAAY;AAC/B,MAAI,aAAa;AACjB,QAAM,YAAY,oBAAI,IAAiE;AAEvF,QAAM,eAAe,MAAM;AACzB,QAAI,aAAa,MAAM;AACrB,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,MAAM,SAAS,IAAI,WAAW,IAAI,kCAAkC;AAAA,QACpE,SAAS;AAAA,QACT,OAAO;AAAA,QACP,QAAQ;AAAA,MACV,CAAC;AACD,iBAAW;AACX,iBAAW,oBAAI,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,aAAW,EAAE,GAAG,MAAM,KAAK,QAAQ;AACjC,UAAM,QAAQ,QAAQ,KAAK;AAC3B,UAAM,OAAO,OAAO,MAAM,SAAS,gBAAgB,gBAAiB,MAAM,QAAQ,EAAG;AAErF,QAAI,SAAS,UAAU,SAAS,aAAa;AAC3C,UAAI,aAAa,KAAM,YAAW;AAClC,eAAS;AACT,eAAS,IAAI,IAAI;AACjB,mBAAa;AAAA,IACf,WAAW,SAAS,aAAa;AAC/B,mBAAa;AACb;AACA,YAAM,OAAQ,MAAM,QAAQ;AAC5B,YAAM,KAAK,OAAO,KAAK,cAAc,QAAQ,SAAS,EAAE;AACxD,gBAAU,IAAI,IAAI,EAAE,MAAM,OAAO,KAAK,YAAY,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;AAAA,IACnF,WAAW,SAAS,eAAe;AACjC,YAAM,KAAK,OAAO,MAAM,cAAc,MAAM,cAAc,EAAE;AAC5D,YAAM,OAAO,UAAU,IAAI,EAAE;AAC7B,UAAI,MAAM;AACR,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,MAAM,KAAK;AAAA;AAAA;AAAA,UAGX,SAAS,KAAK;AAAA,UACd,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,MAAM,EAAE,KAAM,MAAM,WAAW,MAAM,UAA2C,GAAG;AAAA,QACrF,CAAC;AACD,kBAAU,OAAO,EAAE;AAAA,MACrB;AAAA,IACF,WAAW,SAAS,SAAS;AAC3B,YAAM,IAAK,MAAM,SAAS,CAAC;AAC3B,sBAAgB,EAAE,gBAAgB;AAClC,0BAAoB,EAAE,oBAAoB;AAAA,IAC5C;AAAA,EACF;AACA,eAAa;AAEb,QAAM,UAAU,OAAO,SAAS,OAAO,OAAO,SAAS,CAAC,EAAG,IAAI;AAC/D,QAAM,QAAmB,EAAE,OAAO,SAAS,cAAc,kBAAkB,UAAU;AACrF,QAAM,IAAI,MAAM;AAChB,MAAI,MAAM,EAAE,UAAU,QAAQ,EAAE,cAAc,OAAO;AACnD,UAAM,UAAU,eAAe,OAAO,EAAE,UAAU,CAAC,IAAI,mBAAmB,OAAO,EAAE,cAAc,CAAC;AAAA,EACpG;AACA,SAAO;AACT;AAEA,IAAM,OAAO,CAAC,OAAe,IAAI,KAAK,KAAM,QAAQ,CAAC,CAAC;AAG/C,SAAS,gBAAgB,OAAkB,MAAmC;AACnF,QAAM,QAAQ,MAAM,SAAS;AAC7B,QAAM,QAAQ,MAAM,UAAU,IAAI,QAAQ,MAAM,UAAU;AAC1D,QAAM,QAAkB,CAAC;AACzB,QAAM,QAAQ,CAAC,GAAG,MAAM,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AACnE,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,MAAM,CAAC;AACjB,UAAM,SAAS,KAAK,MAAM,EAAE,UAAU,KAAK;AAC3C,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,EAAE,QAAQ,EAAE,WAAW,KAAK,CAAC;AACjE,UAAM,MAAM,IAAI,OAAO,MAAM,KAAK,EAAE,SAAS,SAAS,WAAM,EAAE,SAAS,aAAa,WAAM,UAAK,OAAO,GAAG;AACzG,UAAM,SAAS,MAAM,MAAM,SAAS,IAAI,iBAAO;AAC/C,UAAM,MAAM,GAAG,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE;AAC9D,UAAM,KAAK,GAAG,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,IAAI,MAAM,IAAI,IAAI,OAAO,QAAQ,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,GAAG,GAAG;AAAA,EACnG;AACA,QAAM,OAAO,MAAM,WAAW,OAAO,MAAM,MAAM,QAAQ,QAAQ,MAAM,UAAU,OAAO,IAAI,CAAC,CAAC,KAAK;AACnG,QAAM;AAAA,IACJ,GAAG,KAAK,MAAM,OAAO,EAAE,SAAS,CAAC,CAAC,4BAAe,MAAM,YAAY,OAAO,MAAM,gBAAgB,cAAW,MAAM,SAAS,cAAc,IAAI;AAAA,EAC9I;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAUO,SAAS,UAAU,QAAuC;AAC/D,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC/C,QAAM,IAAI,CAAC,MAAc,OAAO,KAAK,IAAI,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK;AAC/F,SAAO,EAAE,GAAG,OAAO,QAAQ,KAAK,OAAO,CAAC,KAAK,GAAG,KAAK,EAAE,GAAG,GAAG,KAAK,EAAE,GAAG,GAAG,KAAK,OAAO,OAAO,SAAS,CAAC,KAAK,EAAE;AAChH;AAGO,SAAS,gBACd,QACA,MACQ;AACR,MAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,QAAQ,MAAM,SAAS;AAC7B,QAAM,MAAM,MAAM,WAAW,CAAC,MAAc,GAAG,KAAK,MAAM,CAAC,CAAC,GAAG,MAAM,QAAQ,EAAE;AAC/E,QAAM,IAAI,UAAU,MAAM;AAC1B,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE;AAC3C,QAAM,SAAS,IAAI,MAAc,OAAO,EAAE,KAAK,CAAC;AAChD,aAAW,KAAK,QAAQ;AACtB,WAAO,KAAK,IAAI,UAAU,GAAG,KAAK,OAAQ,IAAI,OAAO,KAAK,MAAO,OAAO,CAAC,CAAC;AAAA,EAC5E;AACA,QAAM,WAAW,KAAK,IAAI,GAAG,MAAM;AACnC,QAAM,QAAQ;AAAA,IACZ,KAAK,EAAE,CAAC,SAAS,IAAI,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,GAAG,CAAC;AAAA,EACtF;AACA,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,IAAI,MAAO,KAAK,MAAM,IAAK;AACjC,UAAM,IAAI,MAAO,KAAK,OAAO,IAAI,KAAM;AACvC,UAAM,MAAM,SAAI,OAAO,KAAK,IAAI,OAAO,CAAC,IAAK,IAAI,IAAI,GAAG,KAAK,MAAO,OAAO,CAAC,IAAK,WAAY,KAAK,CAAC,CAAC;AACpG,UAAM,KAAK,GAAG,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,GAAG,IAAI,OAAO,CAAC,CAAC,EAAE;AAAA,EAC5E;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/trace/mission-flow.ts
|
|
2
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
3
|
+
var str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4
|
+
var rec = (v) => v && typeof v === "object" ? v : {};
|
|
5
|
+
var LIVE_DELEGATION_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
|
|
6
|
+
function delegationActivityToFlowSpans(activity, turnStartMs, opts) {
|
|
7
|
+
const spans = [];
|
|
8
|
+
for (const run of activity) {
|
|
9
|
+
const startedAt = Date.parse(run.startedAt);
|
|
10
|
+
if (!Number.isFinite(startedAt)) continue;
|
|
11
|
+
const startMs = startedAt - turnStartMs;
|
|
12
|
+
const live = LIVE_DELEGATION_STATUSES.has(run.status);
|
|
13
|
+
const endMs = run.durationMs !== void 0 ? startMs + run.durationMs : live && opts?.nowMs !== void 0 ? opts.nowMs - turnStartMs : startMs;
|
|
14
|
+
spans.push({
|
|
15
|
+
kind: "tool",
|
|
16
|
+
name: `${run.tool} \u2014 ${run.detail}`,
|
|
17
|
+
startMs,
|
|
18
|
+
endMs: Math.max(endMs, startMs),
|
|
19
|
+
...run.durationMs === void 0 ? { approx: true } : {},
|
|
20
|
+
meta: {
|
|
21
|
+
taskId: run.taskId,
|
|
22
|
+
status: run.status,
|
|
23
|
+
...run.costUsd !== void 0 ? { costUsd: run.costUsd } : {},
|
|
24
|
+
...run.iteration !== void 0 ? { iteration: run.iteration } : {},
|
|
25
|
+
...run.phase !== void 0 ? { phase: run.phase } : {},
|
|
26
|
+
...run.traceId !== void 0 ? { traceId: run.traceId } : {},
|
|
27
|
+
...run.spanId !== void 0 ? { spanId: run.spanId } : {}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return spans;
|
|
32
|
+
}
|
|
33
|
+
function loopTraceEventsToFlowSpans(events) {
|
|
34
|
+
if (events.length === 0) return [];
|
|
35
|
+
const ordered = [...events].sort((a, b) => a.timestamp - b.timestamp);
|
|
36
|
+
const started = ordered.find((e) => e.kind === "loop.started");
|
|
37
|
+
const ended = ordered.find((e) => e.kind === "loop.ended");
|
|
38
|
+
const origin = started?.timestamp ?? ordered[0].timestamp;
|
|
39
|
+
const rootEnd = ended?.timestamp ?? ordered[ordered.length - 1].timestamp;
|
|
40
|
+
const t = (epochMs) => epochMs - origin;
|
|
41
|
+
const spans = [];
|
|
42
|
+
const sp = rec(started?.payload);
|
|
43
|
+
const ep = rec(ended?.payload);
|
|
44
|
+
spans.push({
|
|
45
|
+
kind: "pipeline",
|
|
46
|
+
name: "loop",
|
|
47
|
+
startMs: 0,
|
|
48
|
+
endMs: t(rootEnd),
|
|
49
|
+
meta: {
|
|
50
|
+
runId: ordered[0].runId,
|
|
51
|
+
...str(sp.driver) !== void 0 ? { driver: str(sp.driver) } : {},
|
|
52
|
+
...num(ep.totalCostUsd) !== void 0 ? { costUsd: num(ep.totalCostUsd) } : {},
|
|
53
|
+
...num(ep.winnerIterationIndex) !== void 0 ? { winnerIterationIndex: num(ep.winnerIterationIndex) } : {},
|
|
54
|
+
...num(ep.iterations) !== void 0 ? { iterations: num(ep.iterations) } : {}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const iterStart = /* @__PURE__ */ new Map();
|
|
58
|
+
let round;
|
|
59
|
+
const flushRound = (endEpochMs) => {
|
|
60
|
+
if (!round) return;
|
|
61
|
+
spans.push({
|
|
62
|
+
kind: "pipeline",
|
|
63
|
+
name: `loop \u25B8 round ${round.index} (${round.moveKind})`,
|
|
64
|
+
startMs: round.startMs,
|
|
65
|
+
endMs: t(endEpochMs),
|
|
66
|
+
meta: round.meta
|
|
67
|
+
});
|
|
68
|
+
round = void 0;
|
|
69
|
+
};
|
|
70
|
+
for (const e of ordered) {
|
|
71
|
+
const p = rec(e.payload);
|
|
72
|
+
switch (e.kind) {
|
|
73
|
+
case "loop.plan": {
|
|
74
|
+
flushRound(e.timestamp);
|
|
75
|
+
const index = num(p.roundIndex) ?? 0;
|
|
76
|
+
round = {
|
|
77
|
+
index,
|
|
78
|
+
startMs: t(e.timestamp),
|
|
79
|
+
moveKind: str(p.moveKind) ?? "unknown",
|
|
80
|
+
meta: {
|
|
81
|
+
roundIndex: index,
|
|
82
|
+
moveKind: str(p.moveKind) ?? "unknown",
|
|
83
|
+
width: num(p.plannedCount) ?? 0,
|
|
84
|
+
...str(p.rationale) !== void 0 ? { rationale: str(p.rationale) } : {},
|
|
85
|
+
...num(p.parentIndex) !== void 0 ? { parentIndex: num(p.parentIndex) } : {}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "loop.iteration.started": {
|
|
91
|
+
const idx = num(p.iterationIndex);
|
|
92
|
+
if (idx !== void 0) iterStart.set(idx, e.timestamp);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "loop.iteration.ended": {
|
|
96
|
+
const idx = num(p.iterationIndex) ?? 0;
|
|
97
|
+
const startEpoch = iterStart.get(idx) ?? e.timestamp;
|
|
98
|
+
const error = str(p.error);
|
|
99
|
+
const verdict = rec(p.verdict);
|
|
100
|
+
const tokens = rec(p.tokenUsage);
|
|
101
|
+
const roundLabel = round ? `round ${round.index} \u25B8 ` : "";
|
|
102
|
+
spans.push({
|
|
103
|
+
kind: "model",
|
|
104
|
+
name: `loop \u25B8 ${roundLabel}iter ${idx} (${str(p.agentRunName) ?? "agent"})`,
|
|
105
|
+
startMs: t(startEpoch),
|
|
106
|
+
endMs: t(e.timestamp),
|
|
107
|
+
meta: {
|
|
108
|
+
iterationIndex: idx,
|
|
109
|
+
ok: error === void 0,
|
|
110
|
+
...error !== void 0 ? { error } : {},
|
|
111
|
+
...num(p.costUsd) !== void 0 ? { costUsd: num(p.costUsd) } : {},
|
|
112
|
+
...typeof verdict.valid === "boolean" ? { verdictValid: verdict.valid } : {},
|
|
113
|
+
...num(verdict.score) !== void 0 ? { verdictScore: num(verdict.score) } : {},
|
|
114
|
+
...num(tokens.input) !== void 0 ? { inputTokens: num(tokens.input) } : {},
|
|
115
|
+
...num(tokens.output) !== void 0 ? { outputTokens: num(tokens.output) } : {}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "loop.decision": {
|
|
121
|
+
if (round) {
|
|
122
|
+
const decision = str(p.decision);
|
|
123
|
+
if (decision !== void 0) round.meta.decision = decision;
|
|
124
|
+
flushRound(e.timestamp);
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
flushRound(rootEnd);
|
|
131
|
+
return spans;
|
|
132
|
+
}
|
|
133
|
+
function stepActivityFlowTrace(activity, opts) {
|
|
134
|
+
let origin = opts?.startedAt;
|
|
135
|
+
if (origin === void 0) {
|
|
136
|
+
for (const run of activity) {
|
|
137
|
+
const parsed = Date.parse(run.startedAt);
|
|
138
|
+
if (Number.isFinite(parsed) && (origin === void 0 || parsed < origin)) origin = parsed;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const spans = delegationActivityToFlowSpans(
|
|
142
|
+
activity,
|
|
143
|
+
origin ?? 0,
|
|
144
|
+
opts?.nowMs !== void 0 ? { nowMs: opts.nowMs } : void 0
|
|
145
|
+
);
|
|
146
|
+
let costUsd;
|
|
147
|
+
for (const span of spans) {
|
|
148
|
+
const c = num(rec(span.meta).costUsd);
|
|
149
|
+
if (c !== void 0) costUsd = (costUsd ?? 0) + c;
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
spans,
|
|
153
|
+
totalMs: spans.reduce((max, s) => Math.max(max, s.endMs), 0),
|
|
154
|
+
promptTokens: 0,
|
|
155
|
+
completionTokens: 0,
|
|
156
|
+
...costUsd !== void 0 ? { costUsd } : {},
|
|
157
|
+
toolCalls: spans.length
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function composeMissionFlowTrace(input) {
|
|
161
|
+
const activity = input.activity ?? {};
|
|
162
|
+
const startCandidates = [];
|
|
163
|
+
if (input.startedAt !== void 0) startCandidates.push(input.startedAt);
|
|
164
|
+
else {
|
|
165
|
+
for (const step of input.steps) {
|
|
166
|
+
if (step.startedAt !== void 0) startCandidates.push(step.startedAt);
|
|
167
|
+
}
|
|
168
|
+
for (const runs of Object.values(activity)) {
|
|
169
|
+
for (const run of runs) {
|
|
170
|
+
const parsed = Date.parse(run.startedAt);
|
|
171
|
+
if (Number.isFinite(parsed)) startCandidates.push(parsed);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const origin = startCandidates.length > 0 ? Math.min(...startCandidates) : 0;
|
|
176
|
+
const spans = [];
|
|
177
|
+
let costUsd;
|
|
178
|
+
let toolCalls = 0;
|
|
179
|
+
let cursorMs = 0;
|
|
180
|
+
for (const step of input.steps) {
|
|
181
|
+
const inferred = step.startedAt === void 0;
|
|
182
|
+
const startMs = inferred ? cursorMs : step.startedAt - origin;
|
|
183
|
+
const runSpans = delegationActivityToFlowSpans(activity[step.id] ?? [], origin);
|
|
184
|
+
const runExtent = runSpans.reduce((max, s) => Math.max(max, s.endMs), startMs);
|
|
185
|
+
const endMs = Math.max(step.durationMs !== void 0 ? startMs + step.durationMs : startMs, runExtent);
|
|
186
|
+
spans.push({
|
|
187
|
+
kind: "pipeline",
|
|
188
|
+
name: step.intent,
|
|
189
|
+
startMs,
|
|
190
|
+
endMs,
|
|
191
|
+
...inferred || step.durationMs === void 0 ? { approx: true } : {},
|
|
192
|
+
meta: { stepId: step.id, ...step.status !== void 0 ? { status: step.status } : {} }
|
|
193
|
+
});
|
|
194
|
+
for (const span of runSpans) {
|
|
195
|
+
spans.push({ ...span, name: `${step.intent} \u25B8 ${span.name}` });
|
|
196
|
+
toolCalls++;
|
|
197
|
+
const c = num(rec(span.meta).costUsd);
|
|
198
|
+
if (c !== void 0) costUsd = (costUsd ?? 0) + c;
|
|
199
|
+
}
|
|
200
|
+
cursorMs = endMs;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
spans,
|
|
204
|
+
totalMs: spans.reduce((max, s) => Math.max(max, s.endMs), 0),
|
|
205
|
+
promptTokens: 0,
|
|
206
|
+
completionTokens: 0,
|
|
207
|
+
...costUsd !== void 0 ? { costUsd } : {},
|
|
208
|
+
toolCalls
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export {
|
|
213
|
+
delegationActivityToFlowSpans,
|
|
214
|
+
loopTraceEventsToFlowSpans,
|
|
215
|
+
stepActivityFlowTrace,
|
|
216
|
+
composeMissionFlowTrace
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=chunk-AFDROJ64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/trace/mission-flow.ts"],"sourcesContent":["/**\n * Delegation → FlowSpan converters — render what a mission's delegated agent\n * runs actually did as ONE FlowTrace, drawable by the existing\n * `renderWaterfall` (or any viewer that consumes FlowSpans).\n *\n * Two fidelities, one tree:\n * - COARSE: `delegationActivityToFlowSpans` draws one 'tool' span per\n * delegation from the StepAgentActivity snapshot (startedAt/durationMs) —\n * available live, from the step's journaled lane.\n * - FINE: `loopTraceEventsToFlowSpans` reconstructs agent-runtime's\n * loop → round → iteration hierarchy from the LoopTraceEvent journal a\n * delegation persists. FlowSpans carry no parent ids, so nesting rides the\n * span NAME (`loop ▸ round 0 ▸ iter 1 (coder)`), matching how the ASCII\n * waterfall reads.\n *\n * `composeMissionFlowTrace` lays a whole mission out: one 'pipeline' span per\n * step, each step's delegations beneath it. Pure data transforms — the\n * structural `LoopTraceEventLike` keeps this module free of the optional\n * agent-runtime peer.\n */\n\nimport type { StepAgentActivity } from '../missions/agent-activity'\nimport type { FlowSpan, FlowTrace } from './index'\n\n/** Structural mirror of agent-runtime's `LoopTraceEvent` — same fields, no\n * import, so journals parsed from JSON feed straight in. */\nexport interface LoopTraceEventLike {\n kind: string\n runId: string\n /** Epoch ms. */\n timestamp: number\n payload: object\n}\n\nconst num = (v: unknown): number | undefined =>\n typeof v === 'number' && Number.isFinite(v) ? v : undefined\nconst str = (v: unknown): string | undefined =>\n typeof v === 'string' && v.length > 0 ? v : undefined\nconst rec = (v: unknown): Record<string, unknown> =>\n v && typeof v === 'object' ? (v as Record<string, unknown>) : {}\n\n/** Statuses that mean the delegation is still producing wall time. */\nconst LIVE_DELEGATION_STATUSES = new Set(['pending', 'running'])\n\n/**\n * One 'tool' FlowSpan per delegation, positioned relative to `turnStartMs`\n * (the epoch-ms origin of the trace — usually the step or mission start).\n * A row whose `startedAt` does not parse cannot be placed on a timeline and\n * is omitted from the WATERFALL (it stays in the lane itself). A run without\n * `durationMs` is still in flight: its span extends to `opts.nowMs` when\n * given, else renders as a point — `approx` flags both.\n */\nexport function delegationActivityToFlowSpans(\n activity: StepAgentActivity[],\n turnStartMs: number,\n opts?: { nowMs?: number },\n): FlowSpan[] {\n const spans: FlowSpan[] = []\n for (const run of activity) {\n const startedAt = Date.parse(run.startedAt)\n if (!Number.isFinite(startedAt)) continue\n const startMs = startedAt - turnStartMs\n const live = LIVE_DELEGATION_STATUSES.has(run.status)\n const endMs =\n run.durationMs !== undefined\n ? startMs + run.durationMs\n : live && opts?.nowMs !== undefined\n ? opts.nowMs - turnStartMs\n : startMs\n spans.push({\n kind: 'tool',\n name: `${run.tool} — ${run.detail}`,\n startMs,\n endMs: Math.max(endMs, startMs),\n ...(run.durationMs === undefined ? { approx: true } : {}),\n meta: {\n taskId: run.taskId,\n status: run.status,\n ...(run.costUsd !== undefined ? { costUsd: run.costUsd } : {}),\n ...(run.iteration !== undefined ? { iteration: run.iteration } : {}),\n ...(run.phase !== undefined ? { phase: run.phase } : {}),\n ...(run.traceId !== undefined ? { traceId: run.traceId } : {}),\n ...(run.spanId !== undefined ? { spanId: run.spanId } : {}),\n },\n })\n }\n return spans\n}\n\n/**\n * Reconstruct one delegation's loop → round → iteration tree from its\n * journaled LoopTraceEvents, as FlowSpans relative to `loop.started` (or the\n * first event). Mirrors agent-runtime's `buildLoopOtelSpans` topology:\n * rounds open on `loop.plan` and flush on the next plan / `loop.decision` /\n * `loop.ended`; iterations span `loop.iteration.started` → `.ended`.\n */\nexport function loopTraceEventsToFlowSpans(events: LoopTraceEventLike[]): FlowSpan[] {\n if (events.length === 0) return []\n const ordered = [...events].sort((a, b) => a.timestamp - b.timestamp)\n const started = ordered.find((e) => e.kind === 'loop.started')\n const ended = ordered.find((e) => e.kind === 'loop.ended')\n const origin = started?.timestamp ?? ordered[0]!.timestamp\n const rootEnd = ended?.timestamp ?? ordered[ordered.length - 1]!.timestamp\n const t = (epochMs: number) => epochMs - origin\n\n const spans: FlowSpan[] = []\n const sp = rec(started?.payload)\n const ep = rec(ended?.payload)\n spans.push({\n kind: 'pipeline',\n name: 'loop',\n startMs: 0,\n endMs: t(rootEnd),\n meta: {\n runId: ordered[0]!.runId,\n ...(str(sp.driver) !== undefined ? { driver: str(sp.driver) } : {}),\n ...(num(ep.totalCostUsd) !== undefined ? { costUsd: num(ep.totalCostUsd) } : {}),\n ...(num(ep.winnerIterationIndex) !== undefined\n ? { winnerIterationIndex: num(ep.winnerIterationIndex) }\n : {}),\n ...(num(ep.iterations) !== undefined ? { iterations: num(ep.iterations) } : {}),\n },\n })\n\n const iterStart = new Map<number, number>()\n let round: { index: number; startMs: number; meta: Record<string, unknown>; moveKind: string } | undefined\n const flushRound = (endEpochMs: number) => {\n if (!round) return\n spans.push({\n kind: 'pipeline',\n name: `loop ▸ round ${round.index} (${round.moveKind})`,\n startMs: round.startMs,\n endMs: t(endEpochMs),\n meta: round.meta,\n })\n round = undefined\n }\n\n for (const e of ordered) {\n const p = rec(e.payload)\n switch (e.kind) {\n case 'loop.plan': {\n flushRound(e.timestamp)\n const index = num(p.roundIndex) ?? 0\n round = {\n index,\n startMs: t(e.timestamp),\n moveKind: str(p.moveKind) ?? 'unknown',\n meta: {\n roundIndex: index,\n moveKind: str(p.moveKind) ?? 'unknown',\n width: num(p.plannedCount) ?? 0,\n ...(str(p.rationale) !== undefined ? { rationale: str(p.rationale) } : {}),\n ...(num(p.parentIndex) !== undefined ? { parentIndex: num(p.parentIndex) } : {}),\n },\n }\n break\n }\n case 'loop.iteration.started': {\n const idx = num(p.iterationIndex)\n if (idx !== undefined) iterStart.set(idx, e.timestamp)\n break\n }\n case 'loop.iteration.ended': {\n const idx = num(p.iterationIndex) ?? 0\n const startEpoch = iterStart.get(idx) ?? e.timestamp\n const error = str(p.error)\n const verdict = rec(p.verdict)\n const tokens = rec(p.tokenUsage)\n const roundLabel = round ? `round ${round.index} ▸ ` : ''\n spans.push({\n kind: 'model',\n name: `loop ▸ ${roundLabel}iter ${idx} (${str(p.agentRunName) ?? 'agent'})`,\n startMs: t(startEpoch),\n endMs: t(e.timestamp),\n meta: {\n iterationIndex: idx,\n ok: error === undefined,\n ...(error !== undefined ? { error } : {}),\n ...(num(p.costUsd) !== undefined ? { costUsd: num(p.costUsd) } : {}),\n ...(typeof verdict.valid === 'boolean' ? { verdictValid: verdict.valid } : {}),\n ...(num(verdict.score) !== undefined ? { verdictScore: num(verdict.score) } : {}),\n ...(num(tokens.input) !== undefined ? { inputTokens: num(tokens.input) } : {}),\n ...(num(tokens.output) !== undefined ? { outputTokens: num(tokens.output) } : {}),\n },\n })\n break\n }\n case 'loop.decision': {\n if (round) {\n const decision = str(p.decision)\n if (decision !== undefined) round.meta.decision = decision\n flushRound(e.timestamp)\n }\n break\n }\n }\n }\n flushRound(rootEnd)\n return spans\n}\n\n/**\n * A single step's activity lane as its own FlowTrace — what a per-step\n * drill-in renders. Origin defaults to the earliest delegation start so the\n * waterfall begins at the lane's first run.\n */\nexport function stepActivityFlowTrace(\n activity: StepAgentActivity[],\n opts?: { startedAt?: number; nowMs?: number },\n): FlowTrace {\n let origin = opts?.startedAt\n if (origin === undefined) {\n for (const run of activity) {\n const parsed = Date.parse(run.startedAt)\n if (Number.isFinite(parsed) && (origin === undefined || parsed < origin)) origin = parsed\n }\n }\n const spans = delegationActivityToFlowSpans(\n activity,\n origin ?? 0,\n opts?.nowMs !== undefined ? { nowMs: opts.nowMs } : undefined,\n )\n let costUsd: number | undefined\n for (const span of spans) {\n const c = num(rec(span.meta).costUsd)\n if (c !== undefined) costUsd = (costUsd ?? 0) + c\n }\n return {\n spans,\n totalMs: spans.reduce((max, s) => Math.max(max, s.endMs), 0),\n promptTokens: 0,\n completionTokens: 0,\n ...(costUsd !== undefined ? { costUsd } : {}),\n toolCalls: spans.length,\n }\n}\n\nexport interface MissionFlowStep {\n id: string\n intent: string\n status?: string\n /** Epoch ms the step attempt started. Absent → laid out sequentially after\n * the previous step (missions run steps in order), `approx` flagged. */\n startedAt?: number\n durationMs?: number\n}\n\n/**\n * Compose a mission-wide FlowTrace: one 'pipeline' span per step, the step's\n * delegated runs ('tool' spans, from `activity[stepId]`) beneath it. A step\n * span always covers its delegations' extent. Cost is the sum of delegation\n * `costUsd`; token counts are not knowable from the activity lane and stay 0.\n */\nexport function composeMissionFlowTrace(input: {\n steps: MissionFlowStep[]\n /** Delegated-run snapshots keyed by step id (the step's `agentActivity`). */\n activity?: Record<string, StepAgentActivity[]>\n /** Epoch-ms origin. Default: the earliest known step/delegation start. */\n startedAt?: number\n}): FlowTrace {\n const activity = input.activity ?? {}\n\n const startCandidates: number[] = []\n if (input.startedAt !== undefined) startCandidates.push(input.startedAt)\n else {\n for (const step of input.steps) {\n if (step.startedAt !== undefined) startCandidates.push(step.startedAt)\n }\n for (const runs of Object.values(activity)) {\n for (const run of runs) {\n const parsed = Date.parse(run.startedAt)\n if (Number.isFinite(parsed)) startCandidates.push(parsed)\n }\n }\n }\n const origin = startCandidates.length > 0 ? Math.min(...startCandidates) : 0\n\n const spans: FlowSpan[] = []\n let costUsd: number | undefined\n let toolCalls = 0\n let cursorMs = 0\n\n for (const step of input.steps) {\n const inferred = step.startedAt === undefined\n const startMs = inferred ? cursorMs : step.startedAt! - origin\n const runSpans = delegationActivityToFlowSpans(activity[step.id] ?? [], origin)\n const runExtent = runSpans.reduce((max, s) => Math.max(max, s.endMs), startMs)\n const endMs = Math.max(step.durationMs !== undefined ? startMs + step.durationMs : startMs, runExtent)\n\n spans.push({\n kind: 'pipeline',\n name: step.intent,\n startMs,\n endMs,\n ...(inferred || step.durationMs === undefined ? { approx: true } : {}),\n meta: { stepId: step.id, ...(step.status !== undefined ? { status: step.status } : {}) },\n })\n for (const span of runSpans) {\n spans.push({ ...span, name: `${step.intent} ▸ ${span.name}` })\n toolCalls++\n const c = num(rec(span.meta).costUsd)\n if (c !== undefined) costUsd = (costUsd ?? 0) + c\n }\n cursorMs = endMs\n }\n\n return {\n spans,\n totalMs: spans.reduce((max, s) => Math.max(max, s.endMs), 0),\n promptTokens: 0,\n completionTokens: 0,\n ...(costUsd !== undefined ? { costUsd } : {}),\n toolCalls,\n }\n}\n"],"mappings":"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","names":[]}
|