traceFixture.ts6.8 KBView on GitHub /**
* A hand-built `ExecutionTree` shaped like a real morning fan-out.
*
* root (0 → 10000)
* ├─ agenda-review-a (100 → 4100) ── overlaps b: parallel
* │ └─ draft-subagent-a (200 → 3200)
* │ └─ tool t-a1 (300) completion-only
* ├─ agenda-review-b (150 → 5150)
* │ └─ draft-subagent-b (300 → 4300) FAILED
* │ └─ tool t-b1 (900) completion-only
* ├─ agenda-review-c (5500 → 8500) ── the instrumented branch
* │ ├─ tool t-c1 (5600 → 6500) measured, 900ms
* │ ├─ tool t-c3 (5700 → 5703) measured, 3ms: sub-pixel
* │ ├─ tool t-c4 (6600) measured, 0ms: genuinely instant
* │ └─ tool t-c2 (7500) completion-only
* └─ agenda-review-d (9000 → 9002) ── 2ms: sub-pixel at any width
*
* The MIX is the point. `agent_tool_calls.started_at` is nullable forever, so a
* trace carrying both instrumented and historical rows is the normal case, not
* an edge case — one fixture has to exercise both.
*
* Two deliberate traps are baked into review-c:
* - t-c1 STARTS before t-c3 but FINISHES after it, so anything sorting by
* completion time puts them in the wrong order;
* - t-c4 is a measured 0ms call, which must not render like the unmeasured
* t-c2 even though both carry `durationMs: 0`.
*
* The root's window is 10000ms, so every offset in ms is also 1/100th of a
* percent — which keeps the geometry assertions readable.
*/
import type { ExecutionTree, ExecutionTreeNode, ToolCallSpan } from '@zero/server/execution-tree';
const ROOT_STARTED_AT = new Date('2026-08-09T09:00:00.000Z');
function at(offsetMs: number): Date {
return new Date(ROOT_STARTED_AT.getTime() + offsetMs);
}
/**
* A historical row. `logToolCall` runs after the tool returned, so `created_at`
* is the COMPLETION time and it is the only timestamp that exists — hence
* `durationMs: 0` meaning UNKNOWN, and an offset that is where it ENDED.
*/
function completionOnlyToolCall(
id: string,
runId: string,
toolName: string,
completionOffsetMs: number,
overrides: Partial<ToolCallSpan> = {},
): ToolCallSpan {
return {
kind: 'tool',
id,
runId,
toolName,
arguments: { conversationId: 'conv-1' },
result: { ok: true },
startedAt: null,
createdAt: at(completionOffsetMs),
startOffsetMs: completionOffsetMs,
durationMs: 0,
timingSource: 'completion-only',
...overrides,
};
}
/** An instrumented row: `started_at` is real, so the span is a true interval. */
function measuredToolCall(
id: string,
runId: string,
toolName: string,
startOffsetMs: number,
durationMs: number,
overrides: Partial<ToolCallSpan> = {},
): ToolCallSpan {
return {
kind: 'tool',
id,
runId,
toolName,
arguments: { conversationId: 'conv-1' },
result: { ok: true },
startedAt: at(startOffsetMs),
createdAt: at(startOffsetMs + durationMs),
startOffsetMs,
durationMs,
timingSource: 'measured',
...overrides,
};
}
function execution(
runId: string,
overrides: Partial<ExecutionTreeNode> & Pick<ExecutionTreeNode, 'depth' | 'startOffsetMs'>,
): ExecutionTreeNode {
const durationMs = overrides.durationMs === undefined ? 1000 : overrides.durationMs;
return {
kind: overrides.depth === 0 ? 'execution' : 'subagent',
runId,
parentRunId: null,
status: 'completed',
userId: 'user-1',
conversationId: null,
agentId: null,
agentName: null,
prompt: null,
summary: null,
eventType: null,
source: null,
createdAt: at(overrides.startOffsetMs),
completedAt: durationMs === null ? null : at(overrides.startOffsetMs + durationMs),
childRunIds: [],
toolCalls: [],
...overrides,
durationMs,
};
}
export function buildTraceFixture(): ExecutionTree {
const nodes: ExecutionTreeNode[] = [
execution('root', {
depth: 0,
startOffsetMs: 0,
durationMs: 10_000,
source: 'background-sync',
eventType: 'slack',
childRunIds: ['review-a', 'review-b', 'review-c', 'review-d'],
}),
execution('review-a', {
depth: 1,
startOffsetMs: 100,
durationMs: 4000,
parentRunId: 'root',
agentName: 'review-conversation-a',
conversationId: 'conv-a',
prompt: 'Flagged: no reply in 6 days',
childRunIds: ['subagent-a'],
}),
execution('subagent-a', {
depth: 2,
startOffsetMs: 200,
durationMs: 3000,
parentRunId: 'review-a',
agentName: 'draft-email',
toolCalls: [completionOnlyToolCall('t-a1', 'subagent-a', 'draft-email', 300)],
}),
execution('review-b', {
depth: 1,
startOffsetMs: 150,
durationMs: 5000,
parentRunId: 'root',
agentName: 'review-conversation-b',
childRunIds: ['subagent-b'],
}),
execution('subagent-b', {
depth: 2,
startOffsetMs: 300,
durationMs: 4000,
parentRunId: 'review-b',
agentName: 'enrich-contact',
status: 'failed',
toolCalls: [
completionOnlyToolCall('t-b1', 'subagent-b', 'search-crm', 900, {
result: { error: 'CRM timed out' },
}),
],
}),
// The instrumented branch. Its tool calls sit at depth 2 but are LEAVES, so
// they stay on screen under the default collapse rule — the shapes below are
// what a reader actually sees without a click.
execution('review-c', {
depth: 1,
startOffsetMs: 5500,
durationMs: 3000,
parentRunId: 'root',
agentName: 'review-conversation-c',
toolCalls: [
// Listed out of start order on purpose: the viewer must sort them, and
// must sort them by START, not by the completion time they used to use.
measuredToolCall('t-c3', 'review-c', 'tiny-lookup', 5700, 3),
measuredToolCall('t-c1', 'review-c', 'fetch-thread', 5600, 900),
completionOnlyToolCall('t-c2', 'review-c', 'search-crm-legacy', 7500),
measuredToolCall('t-c4', 'review-c', 'instant-cache-hit', 6600, 0),
],
}),
execution('review-d', {
depth: 1,
startOffsetMs: 9000,
durationMs: 2,
parentRunId: 'root',
agentName: 'review-conversation-d',
}),
];
return {
requestedRunId: 'root',
rootRunId: 'root',
rootStartedAt: ROOT_STARTED_AT,
totalDurationMs: 10_000,
nodes,
nodeCount: nodes.length,
toolCallCount: nodes.reduce((sum, node) => sum + node.toolCalls.length, 0),
maxDepth: 2,
truncated: false,
truncationReasons: [],
};
}
/** Parse a percentage style value written by the waterfall (`'1.5%'` → `1.5`). */
export function pct(value: string): number {
return Number.parseFloat(value.replace('%', ''));
}