@tangle-network/agent-eval 0.74.0 → 0.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "@tangle-network/agent-eval — wire protocol",
5
- "version": "0.74.0",
5
+ "version": "0.76.0",
6
6
  "description": "HTTP and stdio RPC interface to agent-eval. The TypeScript runtime is the source of truth; this spec is the contract that cross-language clients (Python, Rust, Go) generate from.\n\nWire-protocol version: 1.0.0. Bumps on breaking changes to request/response schemas.",
7
7
  "contact": {
8
8
  "name": "Tangle Network",
@@ -1,29 +1,134 @@
1
1
  import { S as Span } from '../schema-m0gsnbt3.js';
2
2
 
3
+ /**
4
+ * Code-edit extraction + the "watch the agent write code" animation.
5
+ *
6
+ * The base storyboard (./index) compresses a run into scenes but keeps scenes
7
+ * payload-free (title + narration + evidence span ids). To actually SHOW the
8
+ * code an agent wrote, this module pulls the concrete edit (path, diff,
9
+ * before/after, language) out of the raw edit-tool spans and renders a
10
+ * self-contained HTML replay that types each file out, character by character,
11
+ * like an editor recording.
12
+ *
13
+ * Additive on purpose: it reuses the canonical `Span` (trace/schema) and the
14
+ * `Storyboard` IR (./index) without modifying the reducer/compiler — so it
15
+ * composes with the existing pipeline and a Remotion consumer can read the same
16
+ * `CodeEdit[]` to render an MP4.
17
+ */
18
+
19
+ /** A concrete code change lifted from a tool span. */
20
+ interface CodeEdit {
21
+ /** Span the edit came from — back-reference into the trace. */
22
+ spanId: string;
23
+ path: string;
24
+ language?: string;
25
+ /** Unified diff, when the tool carried one. */
26
+ diff?: string;
27
+ /** Full file body before / after, when present — enables true keystroke
28
+ * animation rather than only flashing a diff. */
29
+ before?: string;
30
+ after?: string;
31
+ additions: number;
32
+ deletions: number;
33
+ startedAt: number;
34
+ }
35
+ /** Extract a CodeEdit from a single span, or undefined if it is not an edit. */
36
+ declare function codeEditFromSpan(span: Span): CodeEdit | undefined;
37
+ /** All code edits in a run, in chronological order. */
38
+ declare function extractCodeEdits(spans: readonly Span[]): CodeEdit[];
39
+ /** Pair each code-edit (`diff`) scene with its concrete edit, matched via the
40
+ * scene's evidence span ids. Scenes without a recoverable edit are dropped. */
41
+ declare function codeEditsForStoryboard(storyboard: Storyboard, spans: readonly Span[]): Array<{
42
+ scene: Scene;
43
+ edit: CodeEdit;
44
+ }>;
45
+ /** The text the animation types for an edit: prefer the full new file body,
46
+ * then the added lines of a diff, then a minimal placeholder so the scene is
47
+ * never blank. */
48
+ declare function editAnimationText(edit: CodeEdit): string;
49
+ interface CodeAnimationOptions {
50
+ /** Page title. Default "Agent writing code". */
51
+ title?: string;
52
+ /** Characters typed per animation tick. Higher = faster. Default 4. */
53
+ charsPerTick?: number;
54
+ /** Pause between files, ms. Default 900. */
55
+ pauseBetweenMs?: number;
56
+ }
57
+ /**
58
+ * Render a self-contained HTML page that animates the agent writing each file,
59
+ * character by character, with a filename tab, line numbers, and a blinking
60
+ * cursor — the shareable "watch it build" clip. No build step, no assets, no
61
+ * deps. The same `CodeEdit[]` feeds a Remotion MP4 renderer in a consumer
62
+ * package; this is the dependency-free baseline.
63
+ */
64
+ declare function renderCodeAnimationHtml(source: readonly Span[] | readonly CodeEdit[], opts?: CodeAnimationOptions): string;
65
+
3
66
  /**
4
67
  * Storyboard — compile a raw agent trace into a small set of meaningful scenes,
5
- * then render them as a shareable replay.
68
+ * then render them as a shareable, watchable replay.
6
69
  *
7
70
  * Span[] → SemanticEvent[] → Storyboard{scenes} → { markdown | html }
8
71
  *
9
72
  * The trace is the source of truth; the storyboard is the IR; markdown/html
10
73
  * (and, in a consumer package, Remotion/MP4) are compiled targets. Everything
11
74
  * here is PURE — same spans in, same bytes out (no clock/random) — so a run's
12
- * replay is deterministic and diffable. The semantic reducer is rules-based:
13
- * an LLM pass can refine titles later, but the structure does not depend on
14
- * one. Renderers that need a browser (Remotion, React) live in consumers; this
15
- * module only emits strings.
75
+ * replay is deterministic and diffable.
76
+ *
77
+ * Crucially, every scene carries a modality-typed VISUAL extracted from the
78
+ * span a browser/computer screenshot, a code diff, a terminal command +
79
+ * output, file content, an API request/response, or reasoning prose — so no
80
+ * matter what the agent did, the replay SHOWS it, not just narrates it. The
81
+ * reducer is rules-based; an LLM pass can refine titles/summaries later, but
82
+ * the structure + visuals do not depend on one. Renderers that need a browser
83
+ * (Remotion, React) live in consumers; this module only emits strings.
16
84
  */
17
85
 
18
- /** What the agent meaningfully did — the compressed vocabulary the storyboard
19
- * is built from (the spec's semantic layer). */
20
- type SemanticKind = 'understood_task' | 'reasoned' | 'ran_command' | 'read_file' | 'edited_code' | 'searched' | 'used_browser' | 'called_tool' | 'evaluated' | 'observed_failure' | 'completed';
86
+ /** What the agent meaningfully did — the compressed vocabulary. */
87
+ type SemanticKind = 'understood_task' | 'user_message' | 'agent_reply' | 'reasoned' | 'ran_command' | 'read_file' | 'edited_code' | 'searched' | 'used_browser' | 'used_computer' | 'called_api' | 'called_tool' | 'evaluated' | 'observed_failure' | 'completed';
88
+ /** The modality-typed payload a scene shows. This is what makes any agent
89
+ * action — screen, browser, shell, code, API — actually viewable. */
90
+ type SceneVisual = {
91
+ type: 'screenshot';
92
+ src: string;
93
+ caption?: string;
94
+ } | {
95
+ type: 'browser';
96
+ url?: string;
97
+ action?: string;
98
+ screenshot?: string;
99
+ } | {
100
+ type: 'diff';
101
+ path: string;
102
+ patch: string;
103
+ } | {
104
+ type: 'code';
105
+ path: string;
106
+ content: string;
107
+ } | {
108
+ type: 'terminal';
109
+ command: string;
110
+ output?: string;
111
+ } | {
112
+ type: 'api';
113
+ method?: string;
114
+ url: string;
115
+ status?: number;
116
+ request?: string;
117
+ response?: string;
118
+ } | {
119
+ type: 'prose';
120
+ text: string;
121
+ } | {
122
+ type: 'none';
123
+ };
21
124
  interface SemanticEvent {
22
125
  kind: SemanticKind;
23
126
  /** One-line headline (the ticket/scene title). */
24
127
  title: string;
25
128
  /** Short human summary. */
26
129
  summary: string;
130
+ /** The modality payload to show for this moment. */
131
+ visual: SceneVisual;
27
132
  /** Span ids backing this moment — the evidence trail. */
28
133
  evidenceSpanIds: string[];
29
134
  /** 1 (noise) … 5 (pivotal: failures, edits). Drives selection + duration. */
@@ -32,30 +137,30 @@ interface SemanticEvent {
32
137
  endTs: number;
33
138
  }
34
139
  interface ReduceOptions {
35
- /** Collapse adjacent same-kind moments into one (e.g. 6 file reads → one
36
- * "explored N files"). Default true — this is the compression that takes a
37
- * 4000-event run down to dozens of moments. */
140
+ /** Collapse adjacent same-kind moments into one. Default true. */
38
141
  collapseAdjacent?: boolean;
39
142
  }
40
143
  /**
41
- * Reduce a span tree into the meaningful moments a viewer cares about. Spans
42
- * are sorted by start time; each is classified, then adjacent same-kind
43
- * moments collapse into one (the compression step). A failure always stands
44
- * alone (importance 5) so it never gets folded into the noise around it.
144
+ * Reduce a span tree into the meaningful moments a viewer cares about. The
145
+ * conversation (user asks, agent replies) is lifted from LLM spans first so the
146
+ * dialogue is first-class; the remaining work spans are classified + have their
147
+ * modality visual extracted. Adjacent same-kind work moments collapse (the
148
+ * compression step), carrying the latest visual; failures and conversation
149
+ * turns never collapse.
45
150
  */
46
151
  declare function reduceToSemanticEvents(spans: readonly Span[], opts?: ReduceOptions): SemanticEvent[];
47
- type SceneType = 'title_card' | 'reasoning' | 'terminal' | 'file' | 'diff' | 'search' | 'browser' | 'tool' | 'eval' | 'error' | 'summary';
152
+ type SceneType = 'title_card' | 'prompt' | 'reply' | 'reasoning' | 'terminal' | 'file' | 'diff' | 'search' | 'browser' | 'computer' | 'api' | 'tool' | 'eval' | 'error' | 'summary';
48
153
  interface Scene {
49
154
  sceneType: SceneType;
50
155
  title: string;
51
156
  narration: string;
157
+ /** The modality payload the renderer shows. */
158
+ visual: SceneVisual;
52
159
  durationMs: number;
53
- /** Span ids behind the scene — the click-through evidence. */
54
160
  evidenceSpanIds: string[];
55
161
  }
56
162
  interface Storyboard {
57
163
  title: string;
58
- /** Sum of scene durations — the replay length. */
59
164
  totalMs: number;
60
165
  scenes: Scene[];
61
166
  }
@@ -67,10 +172,8 @@ interface CompileOptions {
67
172
  }
68
173
  /**
69
174
  * Select the moments worth showing and lay them out as scenes. Every failure
70
- * (importance 5) and edit (4) is always kept those are the story; the rest
71
- * fill the budget by importance, then everything is re-sorted to chronological
72
- * order so the replay reads forward in time. A title card opens and a summary
73
- * card closes.
175
+ * (importance 5) and edit (4) is always kept; the rest fill the budget by
176
+ * importance, then re-sort chronological. A title card opens, a summary closes.
74
177
  */
75
178
  declare function compileStoryboard(events: readonly SemanticEvent[], opts?: CompileOptions): Storyboard;
76
179
  /** Render the storyboard as a Markdown timeline — the human-readable shot list. */
@@ -82,10 +185,10 @@ interface HtmlRenderOptions {
82
185
  /**
83
186
  * Render the storyboard as a self-contained, auto-playing HTML replay — one
84
187
  * shareable file, no build step, no external assets. Each scene animates in
85
- * for its duration with a progress bar; controls let a viewer scrub. This is
86
- * the "free clip" a run produces; an MP4 is the same storyboard fed to Remotion
87
- * in a consumer package.
188
+ * for its duration and SHOWS its modality (screenshot / diff / terminal / API
189
+ * / browser / prose); controls let a viewer scrub. This is the "free clip" a
190
+ * run produces; an MP4 is the same storyboard fed to Remotion in a consumer.
88
191
  */
89
192
  declare function renderStoryboardHtml(storyboard: Storyboard, opts?: HtmlRenderOptions): string;
90
193
 
91
- export { type CompileOptions, type HtmlRenderOptions, type ReduceOptions, type Scene, type SceneType, type SemanticEvent, type SemanticKind, type Storyboard, compileStoryboard, reduceToSemanticEvents, renderStoryboardHtml, renderStoryboardMarkdown };
194
+ export { type CodeAnimationOptions, type CodeEdit, type CompileOptions, type HtmlRenderOptions, type ReduceOptions, type Scene, type SceneType, type SceneVisual, type SemanticEvent, type SemanticKind, type Storyboard, codeEditFromSpan, codeEditsForStoryboard, compileStoryboard, editAnimationText, extractCodeEdits, reduceToSemanticEvents, renderCodeAnimationHtml, renderStoryboardHtml, renderStoryboardMarkdown };
@@ -1,11 +1,323 @@
1
1
  import "../chunk-PZ5AY32C.js";
2
2
 
3
+ // src/storyboard/code-edit.ts
4
+ var EDIT_TOOLS = /(edit|write|patch|apply|str_replace|create.*file|save|insert|update.*file|multi_edit)/i;
5
+ function languageOf(path) {
6
+ const ext = path.includes(".") ? path.split(".").pop()?.toLowerCase() : void 0;
7
+ if (!ext) return void 0;
8
+ const map = {
9
+ ts: "ts",
10
+ tsx: "tsx",
11
+ js: "js",
12
+ jsx: "jsx",
13
+ mjs: "js",
14
+ cjs: "js",
15
+ py: "python",
16
+ rs: "rust",
17
+ go: "go",
18
+ sol: "solidity",
19
+ java: "java",
20
+ rb: "ruby",
21
+ php: "php",
22
+ c: "c",
23
+ cpp: "cpp",
24
+ cs: "csharp",
25
+ css: "css",
26
+ scss: "scss",
27
+ html: "html",
28
+ json: "json",
29
+ yaml: "yaml",
30
+ yml: "yaml",
31
+ toml: "toml",
32
+ md: "markdown",
33
+ sh: "bash",
34
+ sql: "sql"
35
+ };
36
+ return map[ext] ?? ext;
37
+ }
38
+ function pick(obj, keys) {
39
+ if (!obj || typeof obj !== "object") return void 0;
40
+ const rec = obj;
41
+ for (const k of Object.keys(rec)) {
42
+ if (keys.includes(k.toLowerCase())) {
43
+ const v = rec[k];
44
+ if (typeof v === "string") return v;
45
+ }
46
+ }
47
+ return void 0;
48
+ }
49
+ function countDiff(diff) {
50
+ if (!diff) return { additions: 0, deletions: 0 };
51
+ let additions = 0;
52
+ let deletions = 0;
53
+ for (const line of diff.split("\n")) {
54
+ if (line.startsWith("+") && !line.startsWith("+++")) additions++;
55
+ else if (line.startsWith("-") && !line.startsWith("---")) deletions++;
56
+ }
57
+ return { additions, deletions };
58
+ }
59
+ function codeEditFromSpan(span) {
60
+ if (span.kind !== "tool") return void 0;
61
+ const tool = span;
62
+ if (!EDIT_TOOLS.test(tool.toolName)) return void 0;
63
+ const path = pick(tool.args, ["path", "file_path", "filepath", "filename", "file"]);
64
+ if (!path) return void 0;
65
+ const diff = pick(tool.args, ["diff", "patch"]);
66
+ const after = pick(tool.args, ["content", "new_str", "new_string", "newtext", "text", "body"]);
67
+ const before = pick(tool.args, ["old_str", "old_string", "oldtext", "original"]);
68
+ const counts = countDiff(diff);
69
+ return {
70
+ spanId: span.spanId,
71
+ path,
72
+ language: languageOf(path),
73
+ diff,
74
+ before,
75
+ after,
76
+ additions: diff ? counts.additions : after ? after.split("\n").length : 0,
77
+ deletions: diff ? counts.deletions : 0,
78
+ startedAt: span.startedAt
79
+ };
80
+ }
81
+ function extractCodeEdits(spans) {
82
+ return spans.map(codeEditFromSpan).filter((e) => e != null).sort((a, b) => a.startedAt - b.startedAt);
83
+ }
84
+ function codeEditsForStoryboard(storyboard, spans) {
85
+ const bySpan = new Map(extractCodeEdits(spans).map((e) => [e.spanId, e]));
86
+ const out = [];
87
+ for (const scene of storyboard.scenes) {
88
+ if (scene.sceneType !== "diff") continue;
89
+ const edit = scene.evidenceSpanIds.map((id) => bySpan.get(id)).find((e) => e != null);
90
+ if (edit) out.push({ scene, edit });
91
+ }
92
+ return out;
93
+ }
94
+ function editAnimationText(edit) {
95
+ if (edit.after && edit.after.trim()) return edit.after;
96
+ if (edit.diff) {
97
+ const added = edit.diff.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)).join("\n");
98
+ if (added.trim()) return added;
99
+ }
100
+ return `// ${edit.path}
101
+ // (${edit.additions} line${edit.additions === 1 ? "" : "s"} written)`;
102
+ }
103
+ function esc(s) {
104
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
105
+ }
106
+ function renderCodeAnimationHtml(source, opts = {}) {
107
+ const edits = source.length > 0 && "path" in source[0] ? source : extractCodeEdits(source);
108
+ const files = edits.map((e) => ({
109
+ path: e.path,
110
+ language: e.language ?? "",
111
+ additions: e.additions,
112
+ deletions: e.deletions,
113
+ code: editAnimationText(e)
114
+ }));
115
+ const title = opts.title ?? "Agent writing code";
116
+ const charsPerTick = opts.charsPerTick ?? 4;
117
+ const pauseBetweenMs = opts.pauseBetweenMs ?? 900;
118
+ const filesJson = JSON.stringify(files);
119
+ return `<!doctype html>
120
+ <html lang="en">
121
+ <head>
122
+ <meta charset="utf-8" />
123
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
124
+ <title>${esc(title)}</title>
125
+ <style>
126
+ :root { color-scheme: dark; }
127
+ * { box-sizing: border-box; }
128
+ body { margin: 0; background: #0b0f17; color: #e6edf3;
129
+ font: 14px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
130
+ display: flex; flex-direction: column; min-height: 100vh; align-items: center; justify-content: center; gap: 14px; }
131
+ h1 { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 1rem; font-weight: 600; color: #9aa7b5; margin: 0; }
132
+ .editor { width: min(900px, 94vw); background: #0d1320; border: 1px solid #1e2a3a; border-radius: 12px;
133
+ overflow: hidden; box-shadow: 0 16px 50px rgba(0,0,0,.5); }
134
+ .tabbar { display: flex; align-items: center; gap: 8px; background: #111a29; padding: 9px 14px; border-bottom: 1px solid #1e2a3a; }
135
+ .dot { width: 11px; height: 11px; border-radius: 50%; }
136
+ .dot.r { background: #ff5f56; } .dot.y { background: #ffbd2e; } .dot.g { background: #27c93f; }
137
+ .tab { margin-left: 10px; background: #0d1320; padding: 5px 12px; border-radius: 7px 7px 0 0;
138
+ color: #cdd9e5; font-family: ui-sans-serif, system-ui, sans-serif; font-size: .85rem; }
139
+ .lang { color: #5b6b7d; font-size: .75rem; margin-left: 6px; }
140
+ .adds { margin-left: auto; color: #3fb950; font-size: .78rem; font-family: ui-sans-serif, system-ui, sans-serif; }
141
+ .dels { color: #f85149; margin-left: 8px; }
142
+ .codewrap { display: flex; max-height: 60vh; overflow: auto; }
143
+ .gutter { padding: 14px 10px 14px 14px; text-align: right; color: #3a4757; user-select: none; white-space: pre; }
144
+ pre { margin: 0; padding: 14px 18px 14px 6px; white-space: pre; tab-size: 2; flex: 1; }
145
+ .cursor { display: inline-block; width: 8px; margin-left: 1px; background: #58a6ff; animation: blink .9s steps(1) infinite; }
146
+ @keyframes blink { 50% { opacity: 0; } }
147
+ .controls { display: flex; gap: 10px; align-items: center; font-family: ui-sans-serif, system-ui, sans-serif; font-size: .85rem; color: #8b98a6; }
148
+ button { background: #1b2636; color: #e6edf3; border: 1px solid #2a3a4f; border-radius: 8px; padding: 6px 12px; cursor: pointer; font: inherit; }
149
+ button:hover { background: #243349; }
150
+ .empty { padding: 40px; color: #5b6b7d; }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <h1>\u270F\uFE0F ${esc(title)}</h1>
155
+ <div class="editor">
156
+ <div class="tabbar">
157
+ <span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
158
+ <span class="tab" id="tab">\u2014</span><span class="lang" id="lang"></span>
159
+ <span class="adds" id="adds"></span>
160
+ </div>
161
+ <div class="codewrap"><div class="gutter" id="gutter">1</div><pre id="code"></pre></div>
162
+ </div>
163
+ <div class="controls">
164
+ <button id="pp">\u23F8 Pause</button><button id="restart">\u21BB Restart</button>
165
+ <span id="counter"></span>
166
+ </div>
167
+ <script>
168
+ const FILES = ${filesJson};
169
+ const CPT = ${charsPerTick}, PAUSE = ${pauseBetweenMs};
170
+ const codeEl = document.getElementById('code'), gutterEl = document.getElementById('gutter');
171
+ const tabEl = document.getElementById('tab'), langEl = document.getElementById('lang');
172
+ const addsEl = document.getElementById('adds'), counterEl = document.getElementById('counter');
173
+ let fi = 0, ci = 0, playing = true, raf = 0, paused = false;
174
+ if (!FILES.length) { codeEl.innerHTML = '<span class="empty">No code edits in this run.</span>'; }
175
+ function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
176
+ function setHeader(){ const f = FILES[fi]; tabEl.textContent = f.path; langEl.textContent = f.language || '';
177
+ addsEl.innerHTML = '+' + f.additions + (f.deletions ? ' <span class="dels">-' + f.deletions + '</span>' : '');
178
+ counterEl.textContent = (fi+1) + ' / ' + FILES.length + ' files'; }
179
+ function paint(){ const f = FILES[fi]; const shown = f.code.slice(0, ci);
180
+ const lines = (shown.match(/\\n/g) || []).length + 1;
181
+ gutterEl.textContent = Array.from({length: lines}, (_, k) => k+1).join('\\n');
182
+ codeEl.innerHTML = esc(shown) + '<span class="cursor">&nbsp;</span>';
183
+ const wrap = codeEl.parentElement; wrap.scrollTop = wrap.scrollHeight; }
184
+ function tick(){ if (paused || !FILES.length) return;
185
+ const f = FILES[fi];
186
+ if (ci < f.code.length){ ci = Math.min(f.code.length, ci + CPT); paint(); raf = requestAnimationFrame(tick); }
187
+ else if (fi < FILES.length - 1){ setTimeout(() => { fi++; ci = 0; setHeader(); paint(); raf = requestAnimationFrame(tick); }, PAUSE); }
188
+ else { playing = false; document.getElementById('pp').textContent = '\u25B6 Play'; } }
189
+ document.getElementById('pp').onclick = () => { if (!playing){ playing = true; paused = false; tick(); } else { paused = !paused; if (!paused) tick(); } document.getElementById('pp').textContent = paused ? '\u25B6 Play' : '\u23F8 Pause'; };
190
+ document.getElementById('restart').onclick = () => { fi = 0; ci = 0; playing = true; paused = false; document.getElementById('pp').textContent = '\u23F8 Pause'; setHeader(); paint(); cancelAnimationFrame(raf); tick(); };
191
+ if (FILES.length){ setHeader(); paint(); tick(); }
192
+ </script>
193
+ </body>
194
+ </html>
195
+ `;
196
+ }
197
+
3
198
  // src/storyboard/index.ts
199
+ function str(v) {
200
+ return typeof v === "string" && v.length > 0 ? v : void 0;
201
+ }
202
+ function num(v) {
203
+ return typeof v === "number" ? v : void 0;
204
+ }
205
+ function attr(span, ...keys) {
206
+ const a = span.attributes;
207
+ if (!a) return void 0;
208
+ for (const k of keys) if (a[k] != null) return a[k];
209
+ return void 0;
210
+ }
211
+ function asObj(v) {
212
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
213
+ }
214
+ function safeJson(v) {
215
+ if (v == null) return void 0;
216
+ if (typeof v === "string") return v;
217
+ try {
218
+ return JSON.stringify(v, null, 2);
219
+ } catch {
220
+ return String(v);
221
+ }
222
+ }
223
+ function isImageSrc(s) {
224
+ return !!s && (s.startsWith("data:image") || /^https?:\/\//.test(s));
225
+ }
226
+ function extractVisual(span) {
227
+ const shot = str(attr(span, "screenshot", "screenshotUrl", "image", "frame", "videoFrame")) ?? (span.kind === "tool" ? str(asObj(span.result)?.screenshot) ?? str(asObj(span.result)?.image) : void 0);
228
+ if (span.kind === "tool") {
229
+ const tn = span.toolName.toLowerCase();
230
+ const a = asObj(span.args);
231
+ if (/computer|cua|desktop|\bgui\b|xdotool|mouse|keyboard|screen_|pyautogui/.test(tn)) {
232
+ return {
233
+ type: "browser",
234
+ action: str(a?.action) ?? span.toolName,
235
+ url: str(a?.target),
236
+ screenshot: shot
237
+ };
238
+ }
239
+ if (/browser|playwright|puppeteer|\bpage\b|navigate|goto|\bclick\b|\btype\b|screenshot|dom/.test(
240
+ tn
241
+ )) {
242
+ return {
243
+ type: "browser",
244
+ url: str(a?.url) ?? str(attr(span, "url")),
245
+ action: str(a?.action) ?? str(a?.selector) ?? span.toolName,
246
+ screenshot: shot
247
+ };
248
+ }
249
+ const diff = str(attr(span, "diff", "patch")) ?? str(a?.diff) ?? str(a?.patch);
250
+ if (diff && /edit|write|patch|apply|str_replace|create|save|file/.test(tn)) {
251
+ return {
252
+ type: "diff",
253
+ path: str(a?.path) ?? str(a?.file) ?? str(attr(span, "path")) ?? "",
254
+ patch: truncate(diff, 2e3)
255
+ };
256
+ }
257
+ if (/edit|write|patch|apply|str_replace/.test(tn)) {
258
+ const newText = str(a?.new_str) ?? str(a?.content) ?? str(a?.text);
259
+ return {
260
+ type: "diff",
261
+ path: str(a?.path) ?? str(a?.file) ?? "",
262
+ patch: newText ? `+ ${truncate(newText, 1600)}` : "(no diff captured)"
263
+ };
264
+ }
265
+ if (/read|\bcat\b|open|view|get_file|load|fetch_file/.test(tn)) {
266
+ const content = str(asObj(span.result)?.content) ?? (typeof span.result === "string" ? span.result : void 0) ?? str(attr(span, "content"));
267
+ return {
268
+ type: "code",
269
+ path: str(a?.path) ?? str(a?.file) ?? "",
270
+ content: truncate(content ?? "", 1400)
271
+ };
272
+ }
273
+ if (/search|grep|find|glob|query/.test(tn)) {
274
+ return {
275
+ type: "terminal",
276
+ command: `search ${str(a?.query) ?? str(a?.pattern) ?? ""}`.trim(),
277
+ output: truncate(safeJson(span.result) ?? "", 1e3)
278
+ };
279
+ }
280
+ const url = str(a?.url) ?? str(attr(span, "url"));
281
+ if (url || /http|\bapi\b|fetch|request|webhook|rest|graphql|endpoint/.test(tn)) {
282
+ return {
283
+ type: "api",
284
+ method: str(a?.method) ?? str(attr(span, "method")),
285
+ url: url ?? span.toolName,
286
+ status: num(attr(span, "status", "statusCode")) ?? num(asObj(span.result)?.status),
287
+ request: truncate(str(a?.body) ?? safeJson(span.args) ?? "", 900),
288
+ response: truncate(str(attr(span, "response")) ?? safeJson(span.result) ?? "", 900)
289
+ };
290
+ }
291
+ if (/shell|exec|bash|\brun\b|terminal|command|sandbox|process/.test(tn)) {
292
+ const command = typeof span.args === "string" ? span.args : str(a?.command) ?? str(a?.cmd) ?? span.toolName;
293
+ const output = str(attr(span, "output", "stdout")) ?? (typeof span.result === "string" ? span.result : safeJson(span.result));
294
+ return {
295
+ type: "terminal",
296
+ command: truncate(command, 240),
297
+ output: truncate(output ?? "", 1400)
298
+ };
299
+ }
300
+ return {
301
+ type: "api",
302
+ url: span.toolName,
303
+ request: truncate(safeJson(span.args) ?? "", 900),
304
+ response: truncate(safeJson(span.result) ?? "", 900)
305
+ };
306
+ }
307
+ if (isImageSrc(shot)) return { type: "screenshot", src: shot };
308
+ if (span.kind === "llm") {
309
+ const text = str(span.output) ?? str(span.messages?.[span.messages.length - 1]?.content);
310
+ return text ? { type: "prose", text: truncate(text, 900) } : { type: "none" };
311
+ }
312
+ if (span.kind === "sandbox")
313
+ return { type: "terminal", command: span.name, output: str(attr(span, "output")) };
314
+ return { type: "none" };
315
+ }
4
316
  function toolLabel(span) {
5
317
  if (span.kind === "tool") {
6
- const tn = span.toolName;
7
- const arg = typeof span.args === "string" ? span.args : span.args && typeof span.args === "object" ? Object.values(span.args).find((v) => typeof v === "string") : void 0;
8
- return typeof arg === "string" && arg.length > 0 ? `${tn}: ${truncate(arg, 60)}` : tn;
318
+ const a = asObj(span.args);
319
+ const arg = typeof span.args === "string" ? span.args : a ? str(a.path) ?? str(a.url) ?? str(a.command) ?? str(a.query) ?? Object.values(a).find((v) => typeof v === "string") : void 0;
320
+ return typeof arg === "string" && arg.length > 0 ? `${span.toolName}: ${truncate(arg, 60)}` : span.toolName;
9
321
  }
10
322
  return span.name;
11
323
  }
@@ -13,75 +325,124 @@ function classify(span) {
13
325
  if (span.status === "error" || span.error) {
14
326
  return { kind: "observed_failure", importance: 5, label: span.error ?? span.name };
15
327
  }
16
- if (span.kind === "llm" || span.kind === "agent") {
328
+ if (span.kind === "llm" || span.kind === "agent")
17
329
  return { kind: "reasoned", importance: 2, label: span.name };
18
- }
19
- if (span.kind === "judge") {
20
- return { kind: "evaluated", importance: 2, label: span.name };
21
- }
22
- if (span.kind === "retrieval") {
23
- return { kind: "searched", importance: 2, label: span.name };
24
- }
25
- if (span.kind === "sandbox") {
26
- return { kind: "ran_command", importance: 3, label: span.name };
27
- }
330
+ if (span.kind === "judge") return { kind: "evaluated", importance: 2, label: span.name };
331
+ if (span.kind === "retrieval") return { kind: "searched", importance: 2, label: span.name };
332
+ if (span.kind === "sandbox") return { kind: "ran_command", importance: 3, label: span.name };
28
333
  if (span.kind === "tool") {
29
334
  const tn = span.toolName.toLowerCase();
30
335
  const label = toolLabel(span);
31
- if (/shell|exec|bash|\brun\b|terminal|command/.test(tn)) {
32
- return { kind: "ran_command", importance: 3, label };
33
- }
34
- if (/edit|write|patch|apply|str_replace|create.*file|save/.test(tn)) {
336
+ if (/computer|cua|desktop|\bgui\b|xdotool|pyautogui/.test(tn))
337
+ return { kind: "used_computer", importance: 3, label };
338
+ if (/browser|playwright|puppeteer|\bpage\b|navigate|goto|\bclick\b|screenshot|dom/.test(tn))
339
+ return { kind: "used_browser", importance: 3, label };
340
+ if (/edit|write|patch|apply|str_replace|create.*file|save/.test(tn))
35
341
  return { kind: "edited_code", importance: 4, label };
36
- }
37
- if (/read|\bcat\b|open|view|get.*file|load/.test(tn)) {
342
+ if (/read|\bcat\b|open|view|get.*file|load/.test(tn))
38
343
  return { kind: "read_file", importance: 2, label };
344
+ if (/search|grep|find|glob|query/.test(tn)) return { kind: "searched", importance: 2, label };
345
+ if (/http|\bapi\b|fetch|request|webhook|rest|graphql|endpoint/.test(tn) || str(asObj(span.args)?.url)) {
346
+ return { kind: "called_api", importance: 3, label };
39
347
  }
40
- if (/search|grep|find|glob|query/.test(tn)) {
41
- return { kind: "searched", importance: 2, label };
42
- }
43
- if (/browser|playwright|click|navigate|goto|screenshot|page/.test(tn)) {
44
- return { kind: "used_browser", importance: 3, label };
45
- }
348
+ if (/shell|exec|bash|\brun\b|terminal|command|process/.test(tn))
349
+ return { kind: "ran_command", importance: 3, label };
46
350
  return { kind: "called_tool", importance: 3, label };
47
351
  }
48
352
  return { kind: "called_tool", importance: 2, label: span.name };
49
353
  }
50
354
  var KIND_VERB = {
51
- understood_task: "Received the task",
355
+ understood_task: "Understood the task",
356
+ user_message: "User said",
357
+ agent_reply: "Agent replied",
52
358
  reasoned: "Reasoned",
53
359
  ran_command: "Ran a command",
54
360
  read_file: "Read files",
55
361
  edited_code: "Edited code",
56
362
  searched: "Searched",
57
- used_browser: "Drove the browser",
363
+ used_browser: "Used the browser",
364
+ used_computer: "Controlled the computer",
365
+ called_api: "Called an API",
58
366
  called_tool: "Used a tool",
59
367
  evaluated: "Evaluated the result",
60
368
  observed_failure: "Hit a failure",
61
369
  completed: "Finished"
62
370
  };
371
+ var STANDALONE_KINDS = /* @__PURE__ */ new Set([
372
+ "observed_failure",
373
+ "understood_task",
374
+ "user_message",
375
+ "agent_reply"
376
+ ]);
377
+ function conversationEvents(span, seen, state) {
378
+ if (span.kind !== "llm") return [];
379
+ const out = [];
380
+ const emit = (kind, text, importance) => {
381
+ const t = text.trim();
382
+ if (!t) return;
383
+ const who = kind === "agent_reply" ? "a" : "u";
384
+ const key = `${who}
385
+ ${t}`;
386
+ if (seen.has(key)) return;
387
+ seen.add(key);
388
+ const speaker = kind === "understood_task" ? "Task" : kind === "user_message" ? "User" : "Agent";
389
+ out.push({
390
+ kind,
391
+ title: `${speaker}: ${truncate(t, 64)}`,
392
+ summary: truncate(t, 280),
393
+ visual: { type: "prose", text: truncate(t, 1200) },
394
+ evidenceSpanIds: [span.spanId],
395
+ importance,
396
+ startTs: span.startedAt,
397
+ endTs: span.endedAt ?? span.startedAt
398
+ });
399
+ };
400
+ for (const m of span.messages ?? []) {
401
+ const content = str(m.content);
402
+ if (!content) continue;
403
+ if (m.role === "user") {
404
+ emit(state.sawUser ? "user_message" : "understood_task", content, state.sawUser ? 4 : 5);
405
+ state.sawUser = true;
406
+ } else if (m.role === "assistant") {
407
+ emit("agent_reply", content, 3);
408
+ }
409
+ }
410
+ const reply = str(span.output);
411
+ if (reply) emit("agent_reply", reply, 3);
412
+ return out;
413
+ }
63
414
  function reduceToSemanticEvents(spans, opts = {}) {
64
415
  const collapse = opts.collapseAdjacent ?? true;
65
416
  const ordered = [...spans].sort((a, b) => a.startedAt - b.startedAt);
66
- const raw = ordered.map((s) => {
417
+ const seen = /* @__PURE__ */ new Set();
418
+ const convState = { sawUser: false };
419
+ const raw = [];
420
+ for (const s of ordered) {
421
+ const conv = conversationEvents(s, seen, convState);
422
+ if (conv.length > 0) {
423
+ raw.push(...conv);
424
+ continue;
425
+ }
67
426
  const c = classify(s);
68
- return {
427
+ raw.push({
69
428
  kind: c.kind,
70
429
  title: c.label,
71
430
  summary: `${KIND_VERB[c.kind]} \u2014 ${c.label}`,
431
+ visual: extractVisual(s),
72
432
  evidenceSpanIds: [s.spanId],
73
433
  importance: c.importance,
74
434
  startTs: s.startedAt,
75
435
  endTs: s.endedAt ?? s.startedAt
76
- };
77
- });
436
+ });
437
+ }
78
438
  if (!collapse) return raw;
79
439
  const merged = [];
80
440
  for (const ev of raw) {
81
441
  const prev = merged[merged.length - 1];
82
- if (prev && prev.kind === ev.kind && ev.kind !== "observed_failure") {
442
+ if (prev && prev.kind === ev.kind && !STANDALONE_KINDS.has(ev.kind)) {
83
443
  prev.evidenceSpanIds.push(...ev.evidenceSpanIds);
84
444
  prev.endTs = Math.max(prev.endTs, ev.endTs);
445
+ if (ev.visual.type !== "none") prev.visual = ev.visual;
85
446
  const n = prev.evidenceSpanIds.length;
86
447
  prev.title = `${KIND_VERB[ev.kind]} (${n}\xD7)`;
87
448
  prev.summary = `${KIND_VERB[ev.kind]} ${n} time${n === 1 ? "" : "s"} \u2014 latest: ${ev.title}`;
@@ -93,12 +454,16 @@ function reduceToSemanticEvents(spans, opts = {}) {
93
454
  }
94
455
  var KIND_TO_SCENE = {
95
456
  understood_task: "title_card",
457
+ user_message: "prompt",
458
+ agent_reply: "reply",
96
459
  reasoned: "reasoning",
97
460
  ran_command: "terminal",
98
461
  read_file: "file",
99
462
  edited_code: "diff",
100
463
  searched: "search",
101
464
  used_browser: "browser",
465
+ used_computer: "computer",
466
+ called_api: "api",
102
467
  called_tool: "tool",
103
468
  evaluated: "eval",
104
469
  observed_failure: "error",
@@ -114,7 +479,8 @@ var DURATION_BY_IMPORTANCE = {
114
479
  function compileStoryboard(events, opts = {}) {
115
480
  const title = opts.title ?? "Agent run";
116
481
  const maxScenes = opts.maxScenes ?? 16;
117
- const indexed = events.map((ev, i) => ({ ev, i }));
482
+ const taskEvent = events.find((e) => e.kind === "understood_task");
483
+ const indexed = events.filter((ev) => ev.kind !== "understood_task").map((ev, i) => ({ ev, i }));
118
484
  const mustKeep = indexed.filter(({ ev }) => ev.importance >= 4);
119
485
  const rest = indexed.filter(({ ev }) => ev.importance < 4).sort((a, b) => b.ev.importance - a.ev.importance || a.i - b.i);
120
486
  const budget = Math.max(0, maxScenes - mustKeep.length);
@@ -123,6 +489,7 @@ function compileStoryboard(events, opts = {}) {
123
489
  sceneType: KIND_TO_SCENE[ev.kind],
124
490
  title: ev.title,
125
491
  narration: ev.summary,
492
+ visual: ev.visual,
126
493
  durationMs: DURATION_BY_IMPORTANCE[ev.importance],
127
494
  evidenceSpanIds: ev.evidenceSpanIds
128
495
  }));
@@ -134,15 +501,17 @@ function compileStoryboard(events, opts = {}) {
134
501
  {
135
502
  sceneType: "title_card",
136
503
  title,
137
- narration: "The agent receives its task.",
504
+ narration: taskEvent ? taskEvent.summary : "The agent receives its task.",
505
+ visual: taskEvent ? taskEvent.visual : { type: "none" },
138
506
  durationMs: 3e3,
139
- evidenceSpanIds: []
507
+ evidenceSpanIds: taskEvent ? taskEvent.evidenceSpanIds : []
140
508
  },
141
509
  ...actionScenes,
142
510
  {
143
511
  sceneType: "summary",
144
512
  title: failures > 0 ? "Finished \u2014 with failures" : "Finished",
145
513
  narration: summaryNarration,
514
+ visual: { type: "none" },
146
515
  durationMs: 4e3,
147
516
  evidenceSpanIds: []
148
517
  }
@@ -151,12 +520,16 @@ function compileStoryboard(events, opts = {}) {
151
520
  }
152
521
  var SCENE_ICON = {
153
522
  title_card: "\u{1F3AC}",
523
+ prompt: "\u{1F4AC}",
524
+ reply: "\u{1F916}",
154
525
  reasoning: "\u{1F9E0}",
155
526
  terminal: "\u2328\uFE0F",
156
527
  file: "\u{1F4C4}",
157
528
  diff: "\u270F\uFE0F",
158
529
  search: "\u{1F50E}",
159
530
  browser: "\u{1F310}",
531
+ computer: "\u{1F5A5}\uFE0F",
532
+ api: "\u{1F50C}",
160
533
  tool: "\u{1F527}",
161
534
  eval: "\u2696\uFE0F",
162
535
  error: "\u274C",
@@ -172,6 +545,76 @@ function truncate(s, max) {
172
545
  function escapeHtml(s) {
173
546
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
174
547
  }
548
+ function visualMarkdown(v) {
549
+ switch (v.type) {
550
+ case "screenshot":
551
+ return `\u{1F5BC} screenshot (${truncate(v.src, 60)})`;
552
+ case "browser":
553
+ return `\u{1F310} ${v.action ?? "browser"}${v.url ? ` \u2192 ${v.url}` : ""}${v.screenshot ? " [screenshot]" : ""}`;
554
+ case "diff":
555
+ return `
556
+
557
+ \`\`\`diff
558
+ ${truncate(v.patch, 600)}
559
+ \`\`\`${v.path ? `
560
+ _${v.path}_` : ""}`;
561
+ case "code":
562
+ return `
563
+
564
+ \`\`\`
565
+ ${truncate(v.content, 500)}
566
+ \`\`\`${v.path ? `
567
+ _${v.path}_` : ""}`;
568
+ case "terminal":
569
+ return `
570
+
571
+ \`\`\`sh
572
+ $ ${v.command}${v.output ? `
573
+ ${truncate(v.output, 500)}` : ""}
574
+ \`\`\``;
575
+ case "api":
576
+ return `\u{1F50C} ${v.method ?? "CALL"} ${v.url}${v.status != null ? ` \u2192 ${v.status}` : ""}`;
577
+ case "prose":
578
+ return `
579
+
580
+ > ${truncate(v.text, 400).replace(/\n/g, "\n> ")}`;
581
+ default:
582
+ return "";
583
+ }
584
+ }
585
+ function visualHtml(v) {
586
+ switch (v.type) {
587
+ case "screenshot":
588
+ return `<img class="shot" src="${escapeHtml(v.src)}" alt="screenshot" loading="lazy"/>`;
589
+ case "browser": {
590
+ const bar = `<div class="urlbar">${escapeHtml(v.url ?? v.action ?? "browser")}</div>`;
591
+ const body = isImageSrc(v.screenshot) ? `<img class="shot" src="${escapeHtml(v.screenshot)}" alt="page" loading="lazy"/>` : `<div class="browser-action">${escapeHtml(v.action ?? "navigated")}</div>`;
592
+ return `<div class="browser">${bar}${body}</div>`;
593
+ }
594
+ case "diff": {
595
+ const lines = v.patch.split("\n").map((l) => {
596
+ const cls = l.startsWith("+") ? "add" : l.startsWith("-") ? "del" : "";
597
+ return `<span class="${cls}">${escapeHtml(l)}</span>`;
598
+ });
599
+ return `${v.path ? `<div class="pathhdr">${escapeHtml(v.path)}</div>` : ""}<pre class="diff">${lines.join("\n")}</pre>`;
600
+ }
601
+ case "code":
602
+ return `${v.path ? `<div class="pathhdr">${escapeHtml(v.path)}</div>` : ""}<pre class="code">${escapeHtml(v.content)}</pre>`;
603
+ case "terminal":
604
+ return `<pre class="term"><span class="cmd">$ ${escapeHtml(v.command)}</span>${v.output ? `
605
+ ${escapeHtml(v.output)}` : ""}</pre>`;
606
+ case "api": {
607
+ const badge = `<span class="method">${escapeHtml(v.method ?? "CALL")}</span> <span class="url">${escapeHtml(v.url)}</span>${v.status != null ? ` <span class="status s${Math.floor(v.status / 100)}">${v.status}</span>` : ""}`;
608
+ const req = v.request ? `<div class="kv"><b>req</b><pre>${escapeHtml(v.request)}</pre></div>` : "";
609
+ const res = v.response ? `<div class="kv"><b>res</b><pre>${escapeHtml(v.response)}</pre></div>` : "";
610
+ return `<div class="api"><div class="apihdr">${badge}</div>${req}${res}</div>`;
611
+ }
612
+ case "prose":
613
+ return `<div class="prose">${escapeHtml(v.text)}</div>`;
614
+ default:
615
+ return "";
616
+ }
617
+ }
175
618
  function renderStoryboardMarkdown(storyboard) {
176
619
  const out = [
177
620
  `# \u{1F3AC} ${storyboard.title}`,
@@ -184,11 +627,11 @@ function renderStoryboardMarkdown(storyboard) {
184
627
  out.push(`### [${mmss(elapsed)}] ${SCENE_ICON[sc.sceneType]} ${sc.title}`);
185
628
  out.push("");
186
629
  out.push(sc.narration);
630
+ const vm = visualMarkdown(sc.visual);
631
+ if (vm) out.push(vm);
187
632
  if (sc.evidenceSpanIds.length > 0) {
188
633
  out.push("");
189
- out.push(
190
- `_evidence: ${sc.evidenceSpanIds.length} span(s) \u2014 ${sc.evidenceSpanIds.slice(0, 6).join(", ")}_`
191
- );
634
+ out.push(`_evidence: ${sc.evidenceSpanIds.length} span(s)_`);
192
635
  }
193
636
  out.push("");
194
637
  elapsed += sc.durationMs;
@@ -204,9 +647,10 @@ function renderStoryboardHtml(storyboard, opts = {}) {
204
647
  title: s.title,
205
648
  narration: s.narration,
206
649
  durationMs: s.durationMs,
207
- evidence: s.evidenceSpanIds.length
650
+ evidence: s.evidenceSpanIds.length,
651
+ visual: visualHtml(s.visual)
208
652
  }))
209
- );
653
+ ).replace(/</g, "\\u003c");
210
654
  return `<!doctype html>
211
655
  <html lang="en">
212
656
  <head>
@@ -218,26 +662,42 @@ function renderStoryboardHtml(storyboard, opts = {}) {
218
662
  * { box-sizing: border-box; }
219
663
  body { margin: 0; font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;
220
664
  background: #0b0f17; color: #e6edf3; display: flex; min-height: 100vh; align-items: center; justify-content: center; }
221
- .stage { width: min(820px, 94vw); }
665
+ .stage { width: min(900px, 95vw); }
222
666
  h1 { font-size: 1.1rem; font-weight: 600; color: #9aa7b5; margin: 0 0 14px; letter-spacing: .02em; }
223
- .card { background: #111824; border: 1px solid #1e2a3a; border-radius: 14px; padding: 28px 30px;
224
- min-height: 230px; box-shadow: 0 12px 40px rgba(0,0,0,.45); position: relative; overflow: hidden; }
667
+ .card { background: #111824; border: 1px solid #1e2a3a; border-radius: 14px; padding: 24px 28px;
668
+ min-height: 300px; box-shadow: 0 12px 40px rgba(0,0,0,.45); position: relative; overflow: hidden; }
225
669
  .card::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--accent, #3b82f6); }
226
- .icon { font-size: 2.6rem; line-height: 1; }
227
- .title { font-size: 1.5rem; font-weight: 650; margin: 14px 0 8px; }
228
- .narration { color: #b6c2cf; font-size: 1.05rem; }
229
- .meta { position: absolute; bottom: 16px; right: 22px; color: #5b6b7d; font-size: .8rem; }
670
+ .head { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }
671
+ .icon { font-size: 2rem; line-height: 1; }
672
+ .title { font-size: 1.3rem; font-weight: 650; }
673
+ .narration { color: #9fb0c0; font-size: .95rem; margin-bottom: 14px; }
674
+ .visual { margin-top: 6px; }
675
+ .visual img.shot { max-width: 100%; max-height: 360px; border-radius: 8px; border: 1px solid #1e2a3a; display: block; }
676
+ .visual pre { background: #0a0e15; border: 1px solid #1e2a3a; border-radius: 8px; padding: 12px 14px;
677
+ overflow: auto; max-height: 340px; font: 13px/1.5 ui-monospace, "SF Mono", Menlo, monospace; white-space: pre-wrap; word-break: break-word; }
678
+ .pathhdr { font: 12px ui-monospace, monospace; color: #7d8da0; margin-bottom: 4px; }
679
+ .diff .add { color: #56d364; } .diff .del { color: #f85149; }
680
+ .term .cmd { color: #eab308; }
681
+ .browser { border: 1px solid #1e2a3a; border-radius: 8px; overflow: hidden; }
682
+ .urlbar { background: #0a0e15; padding: 7px 12px; font: 12px ui-monospace, monospace; color: #8b98a6; border-bottom: 1px solid #1e2a3a; }
683
+ .browser-action { padding: 28px; color: #9fb0c0; text-align: center; }
684
+ .api .apihdr { font: 13px ui-monospace, monospace; margin-bottom: 8px; }
685
+ .method { background: #1b2636; padding: 2px 8px; border-radius: 5px; color: #79c0ff; }
686
+ .status.s2 { color: #56d364; } .status.s4, .status.s5 { color: #f85149; }
687
+ .kv { margin-top: 6px; } .kv b { color: #7d8da0; font-weight: 600; font-size: 12px; }
688
+ .prose { color: #c9d4e0; font-size: 1rem; white-space: pre-wrap; max-height: 340px; overflow: auto; }
230
689
  .type-error { --accent: #ef4444; } .type-diff { --accent: #22c55e; } .type-terminal { --accent: #eab308; }
231
- .type-browser { --accent: #06b6d4; } .type-summary { --accent: #a855f7; } .type-title_card { --accent: #3b82f6; }
690
+ .type-browser { --accent: #06b6d4; } .type-computer { --accent: #14b8a6; } .type-api { --accent: #79c0ff; }
691
+ .type-summary { --accent: #a855f7; } .type-title_card { --accent: #3b82f6; }
692
+ .type-prompt { --accent: #f59e0b; } .type-reply { --accent: #10b981; }
232
693
  .scene-enter { animation: pop .45s cubic-bezier(.2,.7,.3,1.2); }
233
694
  @keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.985); } to { opacity: 1; transform: none; } }
234
- .bar { height: 4px; background: #1e2a3a; border-radius: 4px; margin-top: 16px; overflow: hidden; }
695
+ .bar { height: 4px; background: #1e2a3a; border-radius: 4px; margin-top: 14px; overflow: hidden; }
235
696
  .bar > i { display: block; height: 100%; width: 0; background: var(--accent, #3b82f6); transition: width .1s linear; }
236
697
  .controls { display: flex; gap: 10px; align-items: center; margin-top: 14px; color: #8b98a6; font-size: .85rem; }
237
- button { background: #1b2636; color: #e6edf3; border: 1px solid #2a3a4f; border-radius: 8px;
238
- padding: 6px 12px; cursor: pointer; font: inherit; }
698
+ button { background: #1b2636; color: #e6edf3; border: 1px solid #2a3a4f; border-radius: 8px; padding: 6px 12px; cursor: pointer; font: inherit; }
239
699
  button:hover { background: #243349; }
240
- .dots { display: flex; gap: 5px; margin-left: auto; }
700
+ .dots { display: flex; gap: 5px; margin-left: auto; flex-wrap: wrap; }
241
701
  .dot { width: 7px; height: 7px; border-radius: 50%; background: #2a3a4f; cursor: pointer; }
242
702
  .dot.on { background: var(--accent, #3b82f6); }
243
703
  </style>
@@ -246,10 +706,9 @@ function renderStoryboardHtml(storyboard, opts = {}) {
246
706
  <div class="stage">
247
707
  <h1>\u{1F3AC} ${escapeHtml(docTitle)}</h1>
248
708
  <div class="card" id="card">
249
- <div class="icon" id="icon"></div>
250
- <div class="title" id="title"></div>
709
+ <div class="head"><span class="icon" id="icon"></span><span class="title" id="title"></span></div>
251
710
  <div class="narration" id="narration"></div>
252
- <div class="meta" id="meta"></div>
711
+ <div class="visual" id="visual"></div>
253
712
  <div class="bar"><i id="fill"></i></div>
254
713
  </div>
255
714
  <div class="controls">
@@ -263,8 +722,7 @@ function renderStoryboardHtml(storyboard, opts = {}) {
263
722
  <script>
264
723
  const SCENES = ${scenesJson};
265
724
  let i = 0, playing = true, raf = 0, start = 0;
266
- const card = document.getElementById('card'), counter = document.getElementById('counter');
267
- const dotsEl = document.getElementById('dots');
725
+ const card = document.getElementById('card'), counter = document.getElementById('counter'), dotsEl = document.getElementById('dots');
268
726
  SCENES.forEach((_, k) => { const d = document.createElement('span'); d.className = 'dot'; d.onclick = () => go(k); dotsEl.appendChild(d); });
269
727
  function paint() {
270
728
  const s = SCENES[i];
@@ -272,7 +730,7 @@ function renderStoryboardHtml(storyboard, opts = {}) {
272
730
  document.getElementById('icon').textContent = s.icon;
273
731
  document.getElementById('title').textContent = s.title;
274
732
  document.getElementById('narration').textContent = s.narration;
275
- document.getElementById('meta').textContent = s.evidence ? (s.evidence + ' span' + (s.evidence === 1 ? '' : 's')) : '';
733
+ document.getElementById('visual').innerHTML = s.visual || '';
276
734
  counter.textContent = (i + 1) + ' / ' + SCENES.length;
277
735
  [...dotsEl.children].forEach((d, k) => d.className = 'dot' + (k === i ? ' on' : ''));
278
736
  void card.offsetWidth;
@@ -296,8 +754,13 @@ function renderStoryboardHtml(storyboard, opts = {}) {
296
754
  `;
297
755
  }
298
756
  export {
757
+ codeEditFromSpan,
758
+ codeEditsForStoryboard,
299
759
  compileStoryboard,
760
+ editAnimationText,
761
+ extractCodeEdits,
300
762
  reduceToSemanticEvents,
763
+ renderCodeAnimationHtml,
301
764
  renderStoryboardHtml,
302
765
  renderStoryboardMarkdown
303
766
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/storyboard/index.ts"],"sourcesContent":["/**\n * Storyboard — compile a raw agent trace into a small set of meaningful scenes,\n * then render them as a shareable replay.\n *\n * Span[] → SemanticEvent[] → Storyboard{scenes} → { markdown | html }\n *\n * The trace is the source of truth; the storyboard is the IR; markdown/html\n * (and, in a consumer package, Remotion/MP4) are compiled targets. Everything\n * here is PURE — same spans in, same bytes out (no clock/random) — so a run's\n * replay is deterministic and diffable. The semantic reducer is rules-based:\n * an LLM pass can refine titles later, but the structure does not depend on\n * one. Renderers that need a browser (Remotion, React) live in consumers; this\n * module only emits strings.\n */\n\nimport type { Span } from '../trace/schema'\n\n/** What the agent meaningfully did — the compressed vocabulary the storyboard\n * is built from (the spec's semantic layer). */\nexport type SemanticKind =\n | 'understood_task'\n | 'reasoned'\n | 'ran_command'\n | 'read_file'\n | 'edited_code'\n | 'searched'\n | 'used_browser'\n | 'called_tool'\n | 'evaluated'\n | 'observed_failure'\n | 'completed'\n\nexport interface SemanticEvent {\n kind: SemanticKind\n /** One-line headline (the ticket/scene title). */\n title: string\n /** Short human summary. */\n summary: string\n /** Span ids backing this moment — the evidence trail. */\n evidenceSpanIds: string[]\n /** 1 (noise) … 5 (pivotal: failures, edits). Drives selection + duration. */\n importance: 1 | 2 | 3 | 4 | 5\n startTs: number\n endTs: number\n}\n\nexport interface ReduceOptions {\n /** Collapse adjacent same-kind moments into one (e.g. 6 file reads → one\n * \"explored N files\"). Default true — this is the compression that takes a\n * 4000-event run down to dozens of moments. */\n collapseAdjacent?: boolean\n}\n\ninterface Classified {\n kind: SemanticKind\n importance: 1 | 2 | 3 | 4 | 5\n /** A short label for the span's action (e.g. the command, the file). */\n label: string\n}\n\nfunction toolLabel(span: Span): string {\n if (span.kind === 'tool') {\n const tn = span.toolName\n const arg =\n typeof span.args === 'string'\n ? span.args\n : span.args && typeof span.args === 'object'\n ? Object.values(span.args as Record<string, unknown>).find((v) => typeof v === 'string')\n : undefined\n return typeof arg === 'string' && arg.length > 0 ? `${tn}: ${truncate(arg, 60)}` : tn\n }\n return span.name\n}\n\nfunction classify(span: Span): Classified {\n // A failed span is pivotal regardless of what it was doing.\n if (span.status === 'error' || span.error) {\n return { kind: 'observed_failure', importance: 5, label: span.error ?? span.name }\n }\n if (span.kind === 'llm' || span.kind === 'agent') {\n return { kind: 'reasoned', importance: 2, label: span.name }\n }\n if (span.kind === 'judge') {\n return { kind: 'evaluated', importance: 2, label: span.name }\n }\n if (span.kind === 'retrieval') {\n return { kind: 'searched', importance: 2, label: span.name }\n }\n if (span.kind === 'sandbox') {\n return { kind: 'ran_command', importance: 3, label: span.name }\n }\n if (span.kind === 'tool') {\n const tn = span.toolName.toLowerCase()\n const label = toolLabel(span)\n if (/shell|exec|bash|\\brun\\b|terminal|command/.test(tn)) {\n return { kind: 'ran_command', importance: 3, label }\n }\n if (/edit|write|patch|apply|str_replace|create.*file|save/.test(tn)) {\n return { kind: 'edited_code', importance: 4, label }\n }\n if (/read|\\bcat\\b|open|view|get.*file|load/.test(tn)) {\n return { kind: 'read_file', importance: 2, label }\n }\n if (/search|grep|find|glob|query/.test(tn)) {\n return { kind: 'searched', importance: 2, label }\n }\n if (/browser|playwright|click|navigate|goto|screenshot|page/.test(tn)) {\n return { kind: 'used_browser', importance: 3, label }\n }\n return { kind: 'called_tool', importance: 3, label }\n }\n return { kind: 'called_tool', importance: 2, label: span.name }\n}\n\nconst KIND_VERB: Record<SemanticKind, string> = {\n understood_task: 'Received the task',\n reasoned: 'Reasoned',\n ran_command: 'Ran a command',\n read_file: 'Read files',\n edited_code: 'Edited code',\n searched: 'Searched',\n used_browser: 'Drove the browser',\n called_tool: 'Used a tool',\n evaluated: 'Evaluated the result',\n observed_failure: 'Hit a failure',\n completed: 'Finished',\n}\n\n/**\n * Reduce a span tree into the meaningful moments a viewer cares about. Spans\n * are sorted by start time; each is classified, then adjacent same-kind\n * moments collapse into one (the compression step). A failure always stands\n * alone (importance 5) so it never gets folded into the noise around it.\n */\nexport function reduceToSemanticEvents(\n spans: readonly Span[],\n opts: ReduceOptions = {},\n): SemanticEvent[] {\n const collapse = opts.collapseAdjacent ?? true\n const ordered = [...spans].sort((a, b) => a.startedAt - b.startedAt)\n const raw: SemanticEvent[] = ordered.map((s) => {\n const c = classify(s)\n return {\n kind: c.kind,\n title: c.label,\n summary: `${KIND_VERB[c.kind]} — ${c.label}`,\n evidenceSpanIds: [s.spanId],\n importance: c.importance,\n startTs: s.startedAt,\n endTs: s.endedAt ?? s.startedAt,\n }\n })\n if (!collapse) return raw\n\n const merged: SemanticEvent[] = []\n for (const ev of raw) {\n const prev = merged[merged.length - 1]\n // Never fold a failure, and never fold across kinds.\n if (prev && prev.kind === ev.kind && ev.kind !== 'observed_failure') {\n prev.evidenceSpanIds.push(...ev.evidenceSpanIds)\n prev.endTs = Math.max(prev.endTs, ev.endTs)\n const n = prev.evidenceSpanIds.length\n prev.title = `${KIND_VERB[ev.kind]} (${n}×)`\n prev.summary = `${KIND_VERB[ev.kind]} ${n} time${n === 1 ? '' : 's'} — latest: ${ev.title}`\n } else {\n merged.push({ ...ev, evidenceSpanIds: [...ev.evidenceSpanIds] })\n }\n }\n return merged\n}\n\n// ── Storyboard ────────────────────────────────────────────────────────────\n\nexport type SceneType =\n | 'title_card'\n | 'reasoning'\n | 'terminal'\n | 'file'\n | 'diff'\n | 'search'\n | 'browser'\n | 'tool'\n | 'eval'\n | 'error'\n | 'summary'\n\nexport interface Scene {\n sceneType: SceneType\n title: string\n narration: string\n durationMs: number\n /** Span ids behind the scene — the click-through evidence. */\n evidenceSpanIds: string[]\n}\n\nexport interface Storyboard {\n title: string\n /** Sum of scene durations — the replay length. */\n totalMs: number\n scenes: Scene[]\n}\n\nexport interface CompileOptions {\n /** Headline for the title card. Default \"Agent run\". */\n title?: string\n /** Max action scenes between the title + summary cards. Default 16. */\n maxScenes?: number\n}\n\nconst KIND_TO_SCENE: Record<SemanticKind, SceneType> = {\n understood_task: 'title_card',\n reasoned: 'reasoning',\n ran_command: 'terminal',\n read_file: 'file',\n edited_code: 'diff',\n searched: 'search',\n used_browser: 'browser',\n called_tool: 'tool',\n evaluated: 'eval',\n observed_failure: 'error',\n completed: 'summary',\n}\n\n// Pivotal moments earn longer screen time.\nconst DURATION_BY_IMPORTANCE: Record<1 | 2 | 3 | 4 | 5, number> = {\n 1: 2500,\n 2: 3000,\n 3: 4000,\n 4: 5000,\n 5: 6000,\n}\n\n/**\n * Select the moments worth showing and lay them out as scenes. Every failure\n * (importance 5) and edit (4) is always kept — those are the story; the rest\n * fill the budget by importance, then everything is re-sorted to chronological\n * order so the replay reads forward in time. A title card opens and a summary\n * card closes.\n */\nexport function compileStoryboard(\n events: readonly SemanticEvent[],\n opts: CompileOptions = {},\n): Storyboard {\n const title = opts.title ?? 'Agent run'\n const maxScenes = opts.maxScenes ?? 16\n\n const indexed = events.map((ev, i) => ({ ev, i }))\n const mustKeep = indexed.filter(({ ev }) => ev.importance >= 4)\n const rest = indexed\n .filter(({ ev }) => ev.importance < 4)\n .sort((a, b) => b.ev.importance - a.ev.importance || a.i - b.i)\n const budget = Math.max(0, maxScenes - mustKeep.length)\n const selected = [...mustKeep, ...rest.slice(0, budget)].sort((a, b) => a.i - b.i)\n\n const actionScenes: Scene[] = selected.map(({ ev }) => ({\n sceneType: KIND_TO_SCENE[ev.kind],\n title: ev.title,\n narration: ev.summary,\n durationMs: DURATION_BY_IMPORTANCE[ev.importance],\n evidenceSpanIds: ev.evidenceSpanIds,\n }))\n\n const failures = events.filter((e) => e.kind === 'observed_failure').length\n const edits = events.filter((e) => e.kind === 'edited_code').length\n const commands = events.filter((e) => e.kind === 'ran_command').length\n const summaryNarration =\n `${events.length} moments · ${commands} command${commands === 1 ? '' : 's'} · ` +\n `${edits} edit${edits === 1 ? '' : 's'} · ${failures} failure${failures === 1 ? '' : 's'}`\n\n const scenes: Scene[] = [\n {\n sceneType: 'title_card',\n title,\n narration: 'The agent receives its task.',\n durationMs: 3000,\n evidenceSpanIds: [],\n },\n ...actionScenes,\n {\n sceneType: 'summary',\n title: failures > 0 ? 'Finished — with failures' : 'Finished',\n narration: summaryNarration,\n durationMs: 4000,\n evidenceSpanIds: [],\n },\n ]\n return { title, totalMs: scenes.reduce((s, sc) => s + sc.durationMs, 0), scenes }\n}\n\n// ── Renderers (pure string out) ─────────────────────────────────────────────\n\nconst SCENE_ICON: Record<SceneType, string> = {\n title_card: '🎬',\n reasoning: '🧠',\n terminal: '⌨️',\n file: '📄',\n diff: '✏️',\n search: '🔎',\n browser: '🌐',\n tool: '🔧',\n eval: '⚖️',\n error: '❌',\n summary: '🏁',\n}\n\nfunction mmss(ms: number): string {\n const s = Math.round(ms / 1000)\n return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : `${s.slice(0, Math.max(0, max - 1))}…`\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\n/** Render the storyboard as a Markdown timeline — the human-readable shot list. */\nexport function renderStoryboardMarkdown(storyboard: Storyboard): string {\n const out: string[] = [\n `# 🎬 ${storyboard.title}`,\n '',\n `_${storyboard.scenes.length} scenes · ${mmss(storyboard.totalMs)} replay_`,\n '',\n ]\n let elapsed = 0\n for (const sc of storyboard.scenes) {\n out.push(`### [${mmss(elapsed)}] ${SCENE_ICON[sc.sceneType]} ${sc.title}`)\n out.push('')\n out.push(sc.narration)\n if (sc.evidenceSpanIds.length > 0) {\n out.push('')\n out.push(\n `_evidence: ${sc.evidenceSpanIds.length} span(s) — ${sc.evidenceSpanIds.slice(0, 6).join(', ')}_`,\n )\n }\n out.push('')\n elapsed += sc.durationMs\n }\n return out.join('\\n')\n}\n\nexport interface HtmlRenderOptions {\n /** Document title + on-page heading. Defaults to the storyboard title. */\n title?: string\n}\n\n/**\n * Render the storyboard as a self-contained, auto-playing HTML replay — one\n * shareable file, no build step, no external assets. Each scene animates in\n * for its duration with a progress bar; controls let a viewer scrub. This is\n * the \"free clip\" a run produces; an MP4 is the same storyboard fed to Remotion\n * in a consumer package.\n */\nexport function renderStoryboardHtml(storyboard: Storyboard, opts: HtmlRenderOptions = {}): string {\n const docTitle = opts.title ?? storyboard.title\n const scenesJson = JSON.stringify(\n storyboard.scenes.map((s) => ({\n icon: SCENE_ICON[s.sceneType],\n type: s.sceneType,\n title: s.title,\n narration: s.narration,\n durationMs: s.durationMs,\n evidence: s.evidenceSpanIds.length,\n })),\n )\n // Inline everything; the JS player is tiny and dependency-free.\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>${escapeHtml(docTitle)}</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body { margin: 0; font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;\n background: #0b0f17; color: #e6edf3; display: flex; min-height: 100vh; align-items: center; justify-content: center; }\n .stage { width: min(820px, 94vw); }\n h1 { font-size: 1.1rem; font-weight: 600; color: #9aa7b5; margin: 0 0 14px; letter-spacing: .02em; }\n .card { background: #111824; border: 1px solid #1e2a3a; border-radius: 14px; padding: 28px 30px;\n min-height: 230px; box-shadow: 0 12px 40px rgba(0,0,0,.45); position: relative; overflow: hidden; }\n .card::before { content: \"\"; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--accent, #3b82f6); }\n .icon { font-size: 2.6rem; line-height: 1; }\n .title { font-size: 1.5rem; font-weight: 650; margin: 14px 0 8px; }\n .narration { color: #b6c2cf; font-size: 1.05rem; }\n .meta { position: absolute; bottom: 16px; right: 22px; color: #5b6b7d; font-size: .8rem; }\n .type-error { --accent: #ef4444; } .type-diff { --accent: #22c55e; } .type-terminal { --accent: #eab308; }\n .type-browser { --accent: #06b6d4; } .type-summary { --accent: #a855f7; } .type-title_card { --accent: #3b82f6; }\n .scene-enter { animation: pop .45s cubic-bezier(.2,.7,.3,1.2); }\n @keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.985); } to { opacity: 1; transform: none; } }\n .bar { height: 4px; background: #1e2a3a; border-radius: 4px; margin-top: 16px; overflow: hidden; }\n .bar > i { display: block; height: 100%; width: 0; background: var(--accent, #3b82f6); transition: width .1s linear; }\n .controls { display: flex; gap: 10px; align-items: center; margin-top: 14px; color: #8b98a6; font-size: .85rem; }\n button { background: #1b2636; color: #e6edf3; border: 1px solid #2a3a4f; border-radius: 8px;\n padding: 6px 12px; cursor: pointer; font: inherit; }\n button:hover { background: #243349; }\n .dots { display: flex; gap: 5px; margin-left: auto; }\n .dot { width: 7px; height: 7px; border-radius: 50%; background: #2a3a4f; cursor: pointer; }\n .dot.on { background: var(--accent, #3b82f6); }\n</style>\n</head>\n<body>\n<div class=\"stage\">\n <h1>🎬 ${escapeHtml(docTitle)}</h1>\n <div class=\"card\" id=\"card\">\n <div class=\"icon\" id=\"icon\"></div>\n <div class=\"title\" id=\"title\"></div>\n <div class=\"narration\" id=\"narration\"></div>\n <div class=\"meta\" id=\"meta\"></div>\n <div class=\"bar\"><i id=\"fill\"></i></div>\n </div>\n <div class=\"controls\">\n <button id=\"playPause\">⏸ Pause</button>\n <button id=\"prev\">◀ Prev</button>\n <button id=\"next\">Next ▶</button>\n <span id=\"counter\"></span>\n <span class=\"dots\" id=\"dots\"></span>\n </div>\n</div>\n<script>\n const SCENES = ${scenesJson};\n let i = 0, playing = true, raf = 0, start = 0;\n const card = document.getElementById('card'), counter = document.getElementById('counter');\n const dotsEl = document.getElementById('dots');\n SCENES.forEach((_, k) => { const d = document.createElement('span'); d.className = 'dot'; d.onclick = () => go(k); dotsEl.appendChild(d); });\n function paint() {\n const s = SCENES[i];\n card.className = 'card scene-enter type-' + s.type;\n document.getElementById('icon').textContent = s.icon;\n document.getElementById('title').textContent = s.title;\n document.getElementById('narration').textContent = s.narration;\n document.getElementById('meta').textContent = s.evidence ? (s.evidence + ' span' + (s.evidence === 1 ? '' : 's')) : '';\n counter.textContent = (i + 1) + ' / ' + SCENES.length;\n [...dotsEl.children].forEach((d, k) => d.className = 'dot' + (k === i ? ' on' : ''));\n void card.offsetWidth;\n }\n function tick(t) {\n if (!start) start = t;\n const s = SCENES[i], p = Math.min(1, (t - start) / s.durationMs);\n document.getElementById('fill').style.width = (p * 100) + '%';\n if (p >= 1) { if (i < SCENES.length - 1) { go(i + 1); } else { playing = false; updateBtn(); return; } }\n if (playing) raf = requestAnimationFrame(tick);\n }\n function go(n) { i = Math.max(0, Math.min(SCENES.length - 1, n)); start = 0; cancelAnimationFrame(raf); paint(); if (playing) raf = requestAnimationFrame(tick); }\n function updateBtn() { document.getElementById('playPause').textContent = playing ? '⏸ Pause' : '▶ Play'; }\n document.getElementById('playPause').onclick = () => { playing = !playing; updateBtn(); start = 0; if (playing) raf = requestAnimationFrame(tick); else cancelAnimationFrame(raf); };\n document.getElementById('next').onclick = () => go(i + 1);\n document.getElementById('prev').onclick = () => go(i - 1);\n paint(); raf = requestAnimationFrame(tick);\n</script>\n</body>\n</html>\n`\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/storyboard/code-edit.ts","../../src/storyboard/index.ts"],"sourcesContent":["/**\n * Code-edit extraction + the \"watch the agent write code\" animation.\n *\n * The base storyboard (./index) compresses a run into scenes but keeps scenes\n * payload-free (title + narration + evidence span ids). To actually SHOW the\n * code an agent wrote, this module pulls the concrete edit (path, diff,\n * before/after, language) out of the raw edit-tool spans and renders a\n * self-contained HTML replay that types each file out, character by character,\n * like an editor recording.\n *\n * Additive on purpose: it reuses the canonical `Span` (trace/schema) and the\n * `Storyboard` IR (./index) without modifying the reducer/compiler — so it\n * composes with the existing pipeline and a Remotion consumer can read the same\n * `CodeEdit[]` to render an MP4.\n */\n\nimport type { Span } from '../trace/schema'\nimport type { Scene, Storyboard } from './index'\n\nconst EDIT_TOOLS =\n /(edit|write|patch|apply|str_replace|create.*file|save|insert|update.*file|multi_edit)/i\n\n/** A concrete code change lifted from a tool span. */\nexport interface CodeEdit {\n /** Span the edit came from — back-reference into the trace. */\n spanId: string\n path: string\n language?: string\n /** Unified diff, when the tool carried one. */\n diff?: string\n /** Full file body before / after, when present — enables true keystroke\n * animation rather than only flashing a diff. */\n before?: string\n after?: string\n additions: number\n deletions: number\n startedAt: number\n}\n\nfunction languageOf(path: string): string | undefined {\n const ext = path.includes('.') ? path.split('.').pop()?.toLowerCase() : undefined\n if (!ext) return undefined\n const map: Record<string, string> = {\n ts: 'ts',\n tsx: 'tsx',\n js: 'js',\n jsx: 'jsx',\n mjs: 'js',\n cjs: 'js',\n py: 'python',\n rs: 'rust',\n go: 'go',\n sol: 'solidity',\n java: 'java',\n rb: 'ruby',\n php: 'php',\n c: 'c',\n cpp: 'cpp',\n cs: 'csharp',\n css: 'css',\n scss: 'scss',\n html: 'html',\n json: 'json',\n yaml: 'yaml',\n yml: 'yaml',\n toml: 'toml',\n md: 'markdown',\n sh: 'bash',\n sql: 'sql',\n }\n return map[ext] ?? ext\n}\n\nfunction pick(obj: unknown, keys: string[]): string | undefined {\n if (!obj || typeof obj !== 'object') return undefined\n const rec = obj as Record<string, unknown>\n for (const k of Object.keys(rec)) {\n if (keys.includes(k.toLowerCase())) {\n const v = rec[k]\n if (typeof v === 'string') return v\n }\n }\n return undefined\n}\n\nfunction countDiff(diff?: string): { additions: number; deletions: number } {\n if (!diff) return { additions: 0, deletions: 0 }\n let additions = 0\n let deletions = 0\n for (const line of diff.split('\\n')) {\n if (line.startsWith('+') && !line.startsWith('+++')) additions++\n else if (line.startsWith('-') && !line.startsWith('---')) deletions++\n }\n return { additions, deletions }\n}\n\n/** Extract a CodeEdit from a single span, or undefined if it is not an edit. */\nexport function codeEditFromSpan(span: Span): CodeEdit | undefined {\n if (span.kind !== 'tool') return undefined\n const tool = span as Extract<Span, { kind: 'tool' }>\n if (!EDIT_TOOLS.test(tool.toolName)) return undefined\n const path = pick(tool.args, ['path', 'file_path', 'filepath', 'filename', 'file'])\n if (!path) return undefined\n const diff = pick(tool.args, ['diff', 'patch'])\n const after = pick(tool.args, ['content', 'new_str', 'new_string', 'newtext', 'text', 'body'])\n const before = pick(tool.args, ['old_str', 'old_string', 'oldtext', 'original'])\n const counts = countDiff(diff)\n return {\n spanId: span.spanId,\n path,\n language: languageOf(path),\n diff,\n before,\n after,\n additions: diff ? counts.additions : after ? after.split('\\n').length : 0,\n deletions: diff ? counts.deletions : 0,\n startedAt: span.startedAt,\n }\n}\n\n/** All code edits in a run, in chronological order. */\nexport function extractCodeEdits(spans: readonly Span[]): CodeEdit[] {\n return spans\n .map(codeEditFromSpan)\n .filter((e): e is CodeEdit => e != null)\n .sort((a, b) => a.startedAt - b.startedAt)\n}\n\n/** Pair each code-edit (`diff`) scene with its concrete edit, matched via the\n * scene's evidence span ids. Scenes without a recoverable edit are dropped. */\nexport function codeEditsForStoryboard(\n storyboard: Storyboard,\n spans: readonly Span[],\n): Array<{ scene: Scene; edit: CodeEdit }> {\n const bySpan = new Map(extractCodeEdits(spans).map((e) => [e.spanId, e]))\n const out: Array<{ scene: Scene; edit: CodeEdit }> = []\n for (const scene of storyboard.scenes) {\n if (scene.sceneType !== 'diff') continue\n const edit = scene.evidenceSpanIds\n .map((id) => bySpan.get(id))\n .find((e): e is CodeEdit => e != null)\n if (edit) out.push({ scene, edit })\n }\n return out\n}\n\n/** The text the animation types for an edit: prefer the full new file body,\n * then the added lines of a diff, then a minimal placeholder so the scene is\n * never blank. */\nexport function editAnimationText(edit: CodeEdit): string {\n if (edit.after && edit.after.trim()) return edit.after\n if (edit.diff) {\n const added = edit.diff\n .split('\\n')\n .filter((l) => l.startsWith('+') && !l.startsWith('+++'))\n .map((l) => l.slice(1))\n .join('\\n')\n if (added.trim()) return added\n }\n return `// ${edit.path}\\n// (${edit.additions} line${edit.additions === 1 ? '' : 's'} written)`\n}\n\nfunction esc(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nexport interface CodeAnimationOptions {\n /** Page title. Default \"Agent writing code\". */\n title?: string\n /** Characters typed per animation tick. Higher = faster. Default 4. */\n charsPerTick?: number\n /** Pause between files, ms. Default 900. */\n pauseBetweenMs?: number\n}\n\n/**\n * Render a self-contained HTML page that animates the agent writing each file,\n * character by character, with a filename tab, line numbers, and a blinking\n * cursor — the shareable \"watch it build\" clip. No build step, no assets, no\n * deps. The same `CodeEdit[]` feeds a Remotion MP4 renderer in a consumer\n * package; this is the dependency-free baseline.\n */\nexport function renderCodeAnimationHtml(\n source: readonly Span[] | readonly CodeEdit[],\n opts: CodeAnimationOptions = {},\n): string {\n const edits: CodeEdit[] =\n source.length > 0 && 'path' in (source[0] as object)\n ? (source as CodeEdit[])\n : extractCodeEdits(source as readonly Span[])\n\n const files = edits.map((e) => ({\n path: e.path,\n language: e.language ?? '',\n additions: e.additions,\n deletions: e.deletions,\n code: editAnimationText(e),\n }))\n const title = opts.title ?? 'Agent writing code'\n const charsPerTick = opts.charsPerTick ?? 4\n const pauseBetweenMs = opts.pauseBetweenMs ?? 900\n const filesJson = JSON.stringify(files)\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>${esc(title)}</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body { margin: 0; background: #0b0f17; color: #e6edf3;\n font: 14px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n display: flex; flex-direction: column; min-height: 100vh; align-items: center; justify-content: center; gap: 14px; }\n h1 { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 1rem; font-weight: 600; color: #9aa7b5; margin: 0; }\n .editor { width: min(900px, 94vw); background: #0d1320; border: 1px solid #1e2a3a; border-radius: 12px;\n overflow: hidden; box-shadow: 0 16px 50px rgba(0,0,0,.5); }\n .tabbar { display: flex; align-items: center; gap: 8px; background: #111a29; padding: 9px 14px; border-bottom: 1px solid #1e2a3a; }\n .dot { width: 11px; height: 11px; border-radius: 50%; }\n .dot.r { background: #ff5f56; } .dot.y { background: #ffbd2e; } .dot.g { background: #27c93f; }\n .tab { margin-left: 10px; background: #0d1320; padding: 5px 12px; border-radius: 7px 7px 0 0;\n color: #cdd9e5; font-family: ui-sans-serif, system-ui, sans-serif; font-size: .85rem; }\n .lang { color: #5b6b7d; font-size: .75rem; margin-left: 6px; }\n .adds { margin-left: auto; color: #3fb950; font-size: .78rem; font-family: ui-sans-serif, system-ui, sans-serif; }\n .dels { color: #f85149; margin-left: 8px; }\n .codewrap { display: flex; max-height: 60vh; overflow: auto; }\n .gutter { padding: 14px 10px 14px 14px; text-align: right; color: #3a4757; user-select: none; white-space: pre; }\n pre { margin: 0; padding: 14px 18px 14px 6px; white-space: pre; tab-size: 2; flex: 1; }\n .cursor { display: inline-block; width: 8px; margin-left: 1px; background: #58a6ff; animation: blink .9s steps(1) infinite; }\n @keyframes blink { 50% { opacity: 0; } }\n .controls { display: flex; gap: 10px; align-items: center; font-family: ui-sans-serif, system-ui, sans-serif; font-size: .85rem; color: #8b98a6; }\n button { background: #1b2636; color: #e6edf3; border: 1px solid #2a3a4f; border-radius: 8px; padding: 6px 12px; cursor: pointer; font: inherit; }\n button:hover { background: #243349; }\n .empty { padding: 40px; color: #5b6b7d; }\n</style>\n</head>\n<body>\n <h1>✏️ ${esc(title)}</h1>\n <div class=\"editor\">\n <div class=\"tabbar\">\n <span class=\"dot r\"></span><span class=\"dot y\"></span><span class=\"dot g\"></span>\n <span class=\"tab\" id=\"tab\">—</span><span class=\"lang\" id=\"lang\"></span>\n <span class=\"adds\" id=\"adds\"></span>\n </div>\n <div class=\"codewrap\"><div class=\"gutter\" id=\"gutter\">1</div><pre id=\"code\"></pre></div>\n </div>\n <div class=\"controls\">\n <button id=\"pp\">⏸ Pause</button><button id=\"restart\">↻ Restart</button>\n <span id=\"counter\"></span>\n </div>\n<script>\n const FILES = ${filesJson};\n const CPT = ${charsPerTick}, PAUSE = ${pauseBetweenMs};\n const codeEl = document.getElementById('code'), gutterEl = document.getElementById('gutter');\n const tabEl = document.getElementById('tab'), langEl = document.getElementById('lang');\n const addsEl = document.getElementById('adds'), counterEl = document.getElementById('counter');\n let fi = 0, ci = 0, playing = true, raf = 0, paused = false;\n if (!FILES.length) { codeEl.innerHTML = '<span class=\"empty\">No code edits in this run.</span>'; }\n function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}\n function setHeader(){ const f = FILES[fi]; tabEl.textContent = f.path; langEl.textContent = f.language || '';\n addsEl.innerHTML = '+' + f.additions + (f.deletions ? ' <span class=\"dels\">-' + f.deletions + '</span>' : '');\n counterEl.textContent = (fi+1) + ' / ' + FILES.length + ' files'; }\n function paint(){ const f = FILES[fi]; const shown = f.code.slice(0, ci);\n const lines = (shown.match(/\\\\n/g) || []).length + 1;\n gutterEl.textContent = Array.from({length: lines}, (_, k) => k+1).join('\\\\n');\n codeEl.innerHTML = esc(shown) + '<span class=\"cursor\">&nbsp;</span>';\n const wrap = codeEl.parentElement; wrap.scrollTop = wrap.scrollHeight; }\n function tick(){ if (paused || !FILES.length) return;\n const f = FILES[fi];\n if (ci < f.code.length){ ci = Math.min(f.code.length, ci + CPT); paint(); raf = requestAnimationFrame(tick); }\n else if (fi < FILES.length - 1){ setTimeout(() => { fi++; ci = 0; setHeader(); paint(); raf = requestAnimationFrame(tick); }, PAUSE); }\n else { playing = false; document.getElementById('pp').textContent = '▶ Play'; } }\n document.getElementById('pp').onclick = () => { if (!playing){ playing = true; paused = false; tick(); } else { paused = !paused; if (!paused) tick(); } document.getElementById('pp').textContent = paused ? '▶ Play' : '⏸ Pause'; };\n document.getElementById('restart').onclick = () => { fi = 0; ci = 0; playing = true; paused = false; document.getElementById('pp').textContent = '⏸ Pause'; setHeader(); paint(); cancelAnimationFrame(raf); tick(); };\n if (FILES.length){ setHeader(); paint(); tick(); }\n</script>\n</body>\n</html>\n`\n}\n","/**\n * Storyboard — compile a raw agent trace into a small set of meaningful scenes,\n * then render them as a shareable, watchable replay.\n *\n * Span[] → SemanticEvent[] → Storyboard{scenes} → { markdown | html }\n *\n * The trace is the source of truth; the storyboard is the IR; markdown/html\n * (and, in a consumer package, Remotion/MP4) are compiled targets. Everything\n * here is PURE — same spans in, same bytes out (no clock/random) — so a run's\n * replay is deterministic and diffable.\n *\n * Crucially, every scene carries a modality-typed VISUAL extracted from the\n * span — a browser/computer screenshot, a code diff, a terminal command +\n * output, file content, an API request/response, or reasoning prose — so no\n * matter what the agent did, the replay SHOWS it, not just narrates it. The\n * reducer is rules-based; an LLM pass can refine titles/summaries later, but\n * the structure + visuals do not depend on one. Renderers that need a browser\n * (Remotion, React) live in consumers; this module only emits strings.\n */\n\nimport type { Span } from '../trace/schema'\n\n/** What the agent meaningfully did — the compressed vocabulary. */\nexport type SemanticKind =\n | 'understood_task'\n | 'user_message'\n | 'agent_reply'\n | 'reasoned'\n | 'ran_command'\n | 'read_file'\n | 'edited_code'\n | 'searched'\n | 'used_browser'\n | 'used_computer'\n | 'called_api'\n | 'called_tool'\n | 'evaluated'\n | 'observed_failure'\n | 'completed'\n\n/** The modality-typed payload a scene shows. This is what makes any agent\n * action — screen, browser, shell, code, API — actually viewable. */\nexport type SceneVisual =\n | { type: 'screenshot'; src: string; caption?: string }\n | { type: 'browser'; url?: string; action?: string; screenshot?: string }\n | { type: 'diff'; path: string; patch: string }\n | { type: 'code'; path: string; content: string }\n | { type: 'terminal'; command: string; output?: string }\n | {\n type: 'api'\n method?: string\n url: string\n status?: number\n request?: string\n response?: string\n }\n | { type: 'prose'; text: string }\n | { type: 'none' }\n\nexport interface SemanticEvent {\n kind: SemanticKind\n /** One-line headline (the ticket/scene title). */\n title: string\n /** Short human summary. */\n summary: string\n /** The modality payload to show for this moment. */\n visual: SceneVisual\n /** Span ids backing this moment — the evidence trail. */\n evidenceSpanIds: string[]\n /** 1 (noise) … 5 (pivotal: failures, edits). Drives selection + duration. */\n importance: 1 | 2 | 3 | 4 | 5\n startTs: number\n endTs: number\n}\n\nexport interface ReduceOptions {\n /** Collapse adjacent same-kind moments into one. Default true. */\n collapseAdjacent?: boolean\n}\n\n// ── Modality extraction ─────────────────────────────────────────────────────\n\nfunction str(v: unknown): string | undefined {\n return typeof v === 'string' && v.length > 0 ? v : undefined\n}\n\nfunction num(v: unknown): number | undefined {\n return typeof v === 'number' ? v : undefined\n}\n\nfunction attr(span: Span, ...keys: string[]): unknown {\n const a = span.attributes as Record<string, unknown> | undefined\n if (!a) return undefined\n for (const k of keys) if (a[k] != null) return a[k]\n return undefined\n}\n\nfunction asObj(v: unknown): Record<string, unknown> | undefined {\n return v && typeof v === 'object' && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : undefined\n}\n\nfunction safeJson(v: unknown): string | undefined {\n if (v == null) return undefined\n if (typeof v === 'string') return v\n try {\n return JSON.stringify(v, null, 2)\n } catch {\n return String(v)\n }\n}\n\n/** Is this string an embeddable image (data URI or http URL)? */\nfunction isImageSrc(s: string | undefined): s is string {\n return !!s && (s.startsWith('data:image') || /^https?:\\/\\//.test(s))\n}\n\n/** Pull the modality-appropriate visual out of whatever the span carries.\n * Conventions (checked in priority order): an image rides on\n * `attributes.screenshot|image|frame|videoFrame` or `result.screenshot`; a\n * diff on `attributes.diff|patch` or `args.diff`; a command on `args` /\n * `args.command`; an API call on a `url`/`method` in args; file content on\n * `result`. Unknown tools degrade to an args/result inspector. */\nfunction extractVisual(span: Span): SceneVisual {\n const shot =\n str(attr(span, 'screenshot', 'screenshotUrl', 'image', 'frame', 'videoFrame')) ??\n (span.kind === 'tool'\n ? (str(asObj(span.result)?.screenshot) ?? str(asObj(span.result)?.image))\n : undefined)\n\n if (span.kind === 'tool') {\n const tn = span.toolName.toLowerCase()\n const a = asObj(span.args)\n // computer use (GUI / desktop control)\n if (/computer|cua|desktop|\\bgui\\b|xdotool|mouse|keyboard|screen_|pyautogui/.test(tn)) {\n return {\n type: 'browser',\n action: str(a?.action) ?? span.toolName,\n url: str(a?.target),\n screenshot: shot,\n }\n }\n // browser use\n if (\n /browser|playwright|puppeteer|\\bpage\\b|navigate|goto|\\bclick\\b|\\btype\\b|screenshot|dom/.test(\n tn,\n )\n ) {\n return {\n type: 'browser',\n url: str(a?.url) ?? str(attr(span, 'url')),\n action: str(a?.action) ?? str(a?.selector) ?? span.toolName,\n screenshot: shot,\n }\n }\n // code edit → diff\n const diff = str(attr(span, 'diff', 'patch')) ?? str(a?.diff) ?? str(a?.patch)\n if (diff && /edit|write|patch|apply|str_replace|create|save|file/.test(tn)) {\n return {\n type: 'diff',\n path: str(a?.path) ?? str(a?.file) ?? str(attr(span, 'path')) ?? '',\n patch: truncate(diff, 2000),\n }\n }\n if (/edit|write|patch|apply|str_replace/.test(tn)) {\n const newText = str(a?.new_str) ?? str(a?.content) ?? str(a?.text)\n return {\n type: 'diff',\n path: str(a?.path) ?? str(a?.file) ?? '',\n patch: newText ? `+ ${truncate(newText, 1600)}` : '(no diff captured)',\n }\n }\n // file read → code\n if (/read|\\bcat\\b|open|view|get_file|load|fetch_file/.test(tn)) {\n const content =\n str(asObj(span.result)?.content) ??\n (typeof span.result === 'string' ? span.result : undefined) ??\n str(attr(span, 'content'))\n return {\n type: 'code',\n path: str(a?.path) ?? str(a?.file) ?? '',\n content: truncate(content ?? '', 1400),\n }\n }\n // search\n if (/search|grep|find|glob|query/.test(tn)) {\n return {\n type: 'terminal',\n command: `search ${str(a?.query) ?? str(a?.pattern) ?? ''}`.trim(),\n output: truncate(safeJson(span.result) ?? '', 1000),\n }\n }\n // api / http / network\n const url = str(a?.url) ?? str(attr(span, 'url'))\n if (url || /http|\\bapi\\b|fetch|request|webhook|rest|graphql|endpoint/.test(tn)) {\n return {\n type: 'api',\n method: str(a?.method) ?? str(attr(span, 'method')),\n url: url ?? span.toolName,\n status: num(attr(span, 'status', 'statusCode')) ?? num(asObj(span.result)?.status),\n request: truncate(str(a?.body) ?? safeJson(span.args) ?? '', 900),\n response: truncate(str(attr(span, 'response')) ?? safeJson(span.result) ?? '', 900),\n }\n }\n // shell / sandbox exec\n if (/shell|exec|bash|\\brun\\b|terminal|command|sandbox|process/.test(tn)) {\n const command =\n typeof span.args === 'string'\n ? span.args\n : (str(a?.command) ?? str(a?.cmd) ?? span.toolName)\n const output =\n str(attr(span, 'output', 'stdout')) ??\n (typeof span.result === 'string' ? span.result : safeJson(span.result))\n return {\n type: 'terminal',\n command: truncate(command, 240),\n output: truncate(output ?? '', 1400),\n }\n }\n // generic tool — show the call\n return {\n type: 'api',\n url: span.toolName,\n request: truncate(safeJson(span.args) ?? '', 900),\n response: truncate(safeJson(span.result) ?? '', 900),\n }\n }\n\n if (isImageSrc(shot)) return { type: 'screenshot', src: shot }\n if (span.kind === 'llm') {\n const text = str(span.output) ?? str(span.messages?.[span.messages.length - 1]?.content)\n return text ? { type: 'prose', text: truncate(text, 900) } : { type: 'none' }\n }\n if (span.kind === 'sandbox')\n return { type: 'terminal', command: span.name, output: str(attr(span, 'output')) }\n return { type: 'none' }\n}\n\n// ── Classification ──────────────────────────────────────────────────────────\n\ninterface Classified {\n kind: SemanticKind\n importance: 1 | 2 | 3 | 4 | 5\n label: string\n}\n\nfunction toolLabel(span: Span): string {\n if (span.kind === 'tool') {\n const a = asObj(span.args)\n const arg =\n typeof span.args === 'string'\n ? span.args\n : a\n ? (str(a.path) ??\n str(a.url) ??\n str(a.command) ??\n str(a.query) ??\n Object.values(a).find((v) => typeof v === 'string'))\n : undefined\n return typeof arg === 'string' && arg.length > 0\n ? `${span.toolName}: ${truncate(arg, 60)}`\n : span.toolName\n }\n return span.name\n}\n\nfunction classify(span: Span): Classified {\n if (span.status === 'error' || span.error) {\n return { kind: 'observed_failure', importance: 5, label: span.error ?? span.name }\n }\n if (span.kind === 'llm' || span.kind === 'agent')\n return { kind: 'reasoned', importance: 2, label: span.name }\n if (span.kind === 'judge') return { kind: 'evaluated', importance: 2, label: span.name }\n if (span.kind === 'retrieval') return { kind: 'searched', importance: 2, label: span.name }\n if (span.kind === 'sandbox') return { kind: 'ran_command', importance: 3, label: span.name }\n if (span.kind === 'tool') {\n const tn = span.toolName.toLowerCase()\n const label = toolLabel(span)\n if (/computer|cua|desktop|\\bgui\\b|xdotool|pyautogui/.test(tn))\n return { kind: 'used_computer', importance: 3, label }\n if (/browser|playwright|puppeteer|\\bpage\\b|navigate|goto|\\bclick\\b|screenshot|dom/.test(tn))\n return { kind: 'used_browser', importance: 3, label }\n if (/edit|write|patch|apply|str_replace|create.*file|save/.test(tn))\n return { kind: 'edited_code', importance: 4, label }\n if (/read|\\bcat\\b|open|view|get.*file|load/.test(tn))\n return { kind: 'read_file', importance: 2, label }\n if (/search|grep|find|glob|query/.test(tn)) return { kind: 'searched', importance: 2, label }\n if (\n /http|\\bapi\\b|fetch|request|webhook|rest|graphql|endpoint/.test(tn) ||\n str(asObj(span.args)?.url)\n ) {\n return { kind: 'called_api', importance: 3, label }\n }\n if (/shell|exec|bash|\\brun\\b|terminal|command|process/.test(tn))\n return { kind: 'ran_command', importance: 3, label }\n return { kind: 'called_tool', importance: 3, label }\n }\n return { kind: 'called_tool', importance: 2, label: span.name }\n}\n\nconst KIND_VERB: Record<SemanticKind, string> = {\n understood_task: 'Understood the task',\n user_message: 'User said',\n agent_reply: 'Agent replied',\n reasoned: 'Reasoned',\n ran_command: 'Ran a command',\n read_file: 'Read files',\n edited_code: 'Edited code',\n searched: 'Searched',\n used_browser: 'Used the browser',\n used_computer: 'Controlled the computer',\n called_api: 'Called an API',\n called_tool: 'Used a tool',\n evaluated: 'Evaluated the result',\n observed_failure: 'Hit a failure',\n completed: 'Finished',\n}\n\n/** Kinds that must never collapse into a neighbour — each is its own beat:\n * a failure, and every conversation turn (so the dialogue reads in full). */\nconst STANDALONE_KINDS = new Set<SemanticKind>([\n 'observed_failure',\n 'understood_task',\n 'user_message',\n 'agent_reply',\n])\n\n/** Lift the conversation turns out of an LLM span. The user/assistant text\n * lives in `LlmSpan.messages` (role:'user'|'assistant') and the new reply in\n * `output`; the full history repeats across spans, so `seen` dedupes turns to\n * the first time each is said. The first user turn becomes `understood_task`\n * (the premise), later user turns `user_message`, assistant text `agent_reply`. */\nfunction conversationEvents(\n span: Span,\n seen: Set<string>,\n state: { sawUser: boolean },\n): SemanticEvent[] {\n if (span.kind !== 'llm') return []\n const out: SemanticEvent[] = []\n const emit = (kind: SemanticKind, text: string, importance: 1 | 2 | 3 | 4 | 5) => {\n const t = text.trim()\n if (!t) return\n const who = kind === 'agent_reply' ? 'a' : 'u'\n const key = `${who}\\n${t}`\n if (seen.has(key)) return\n seen.add(key)\n const speaker = kind === 'understood_task' ? 'Task' : kind === 'user_message' ? 'User' : 'Agent'\n out.push({\n kind,\n title: `${speaker}: ${truncate(t, 64)}`,\n summary: truncate(t, 280),\n visual: { type: 'prose', text: truncate(t, 1200) },\n evidenceSpanIds: [span.spanId],\n importance,\n startTs: span.startedAt,\n endTs: span.endedAt ?? span.startedAt,\n })\n }\n for (const m of span.messages ?? []) {\n const content = str(m.content)\n if (!content) continue\n if (m.role === 'user') {\n emit(state.sawUser ? 'user_message' : 'understood_task', content, state.sawUser ? 4 : 5)\n state.sawUser = true\n } else if (m.role === 'assistant') {\n emit('agent_reply', content, 3)\n }\n }\n const reply = str(span.output)\n if (reply) emit('agent_reply', reply, 3)\n return out\n}\n\n/**\n * Reduce a span tree into the meaningful moments a viewer cares about. The\n * conversation (user asks, agent replies) is lifted from LLM spans first so the\n * dialogue is first-class; the remaining work spans are classified + have their\n * modality visual extracted. Adjacent same-kind work moments collapse (the\n * compression step), carrying the latest visual; failures and conversation\n * turns never collapse.\n */\nexport function reduceToSemanticEvents(\n spans: readonly Span[],\n opts: ReduceOptions = {},\n): SemanticEvent[] {\n const collapse = opts.collapseAdjacent ?? true\n const ordered = [...spans].sort((a, b) => a.startedAt - b.startedAt)\n const seen = new Set<string>()\n const convState = { sawUser: false }\n const raw: SemanticEvent[] = []\n for (const s of ordered) {\n const conv = conversationEvents(s, seen, convState)\n if (conv.length > 0) {\n // this LLM span IS dialogue — surface the turns, not a generic \"reasoned\"\n raw.push(...conv)\n continue\n }\n const c = classify(s)\n raw.push({\n kind: c.kind,\n title: c.label,\n summary: `${KIND_VERB[c.kind]} — ${c.label}`,\n visual: extractVisual(s),\n evidenceSpanIds: [s.spanId],\n importance: c.importance,\n startTs: s.startedAt,\n endTs: s.endedAt ?? s.startedAt,\n })\n }\n if (!collapse) return raw\n\n const merged: SemanticEvent[] = []\n for (const ev of raw) {\n const prev = merged[merged.length - 1]\n if (prev && prev.kind === ev.kind && !STANDALONE_KINDS.has(ev.kind)) {\n prev.evidenceSpanIds.push(...ev.evidenceSpanIds)\n prev.endTs = Math.max(prev.endTs, ev.endTs)\n if (ev.visual.type !== 'none') prev.visual = ev.visual // keep the latest state\n const n = prev.evidenceSpanIds.length\n prev.title = `${KIND_VERB[ev.kind]} (${n}×)`\n prev.summary = `${KIND_VERB[ev.kind]} ${n} time${n === 1 ? '' : 's'} — latest: ${ev.title}`\n } else {\n merged.push({ ...ev, evidenceSpanIds: [...ev.evidenceSpanIds] })\n }\n }\n return merged\n}\n\n// ── Storyboard ────────────────────────────────────────────────────────────\n\nexport type SceneType =\n | 'title_card'\n | 'prompt'\n | 'reply'\n | 'reasoning'\n | 'terminal'\n | 'file'\n | 'diff'\n | 'search'\n | 'browser'\n | 'computer'\n | 'api'\n | 'tool'\n | 'eval'\n | 'error'\n | 'summary'\n\nexport interface Scene {\n sceneType: SceneType\n title: string\n narration: string\n /** The modality payload the renderer shows. */\n visual: SceneVisual\n durationMs: number\n evidenceSpanIds: string[]\n}\n\nexport interface Storyboard {\n title: string\n totalMs: number\n scenes: Scene[]\n}\n\nexport interface CompileOptions {\n /** Headline for the title card. Default \"Agent run\". */\n title?: string\n /** Max action scenes between the title + summary cards. Default 16. */\n maxScenes?: number\n}\n\nconst KIND_TO_SCENE: Record<SemanticKind, SceneType> = {\n understood_task: 'title_card',\n user_message: 'prompt',\n agent_reply: 'reply',\n reasoned: 'reasoning',\n ran_command: 'terminal',\n read_file: 'file',\n edited_code: 'diff',\n searched: 'search',\n used_browser: 'browser',\n used_computer: 'computer',\n called_api: 'api',\n called_tool: 'tool',\n evaluated: 'eval',\n observed_failure: 'error',\n completed: 'summary',\n}\n\nconst DURATION_BY_IMPORTANCE: Record<1 | 2 | 3 | 4 | 5, number> = {\n 1: 2500,\n 2: 3000,\n 3: 4000,\n 4: 5000,\n 5: 6000,\n}\n\n/**\n * Select the moments worth showing and lay them out as scenes. Every failure\n * (importance 5) and edit (4) is always kept; the rest fill the budget by\n * importance, then re-sort chronological. A title card opens, a summary closes.\n */\nexport function compileStoryboard(\n events: readonly SemanticEvent[],\n opts: CompileOptions = {},\n): Storyboard {\n const title = opts.title ?? 'Agent run'\n const maxScenes = opts.maxScenes ?? 16\n\n // The first user turn is the premise — it frames the title card rather than\n // being shown twice, so it's excluded from the action selection below.\n const taskEvent = events.find((e) => e.kind === 'understood_task')\n\n const indexed = events.filter((ev) => ev.kind !== 'understood_task').map((ev, i) => ({ ev, i }))\n const mustKeep = indexed.filter(({ ev }) => ev.importance >= 4)\n const rest = indexed\n .filter(({ ev }) => ev.importance < 4)\n .sort((a, b) => b.ev.importance - a.ev.importance || a.i - b.i)\n const budget = Math.max(0, maxScenes - mustKeep.length)\n const selected = [...mustKeep, ...rest.slice(0, budget)].sort((a, b) => a.i - b.i)\n\n const actionScenes: Scene[] = selected.map(({ ev }) => ({\n sceneType: KIND_TO_SCENE[ev.kind],\n title: ev.title,\n narration: ev.summary,\n visual: ev.visual,\n durationMs: DURATION_BY_IMPORTANCE[ev.importance],\n evidenceSpanIds: ev.evidenceSpanIds,\n }))\n\n const failures = events.filter((e) => e.kind === 'observed_failure').length\n const edits = events.filter((e) => e.kind === 'edited_code').length\n const commands = events.filter((e) => e.kind === 'ran_command').length\n const summaryNarration =\n `${events.length} moments · ${commands} command${commands === 1 ? '' : 's'} · ` +\n `${edits} edit${edits === 1 ? '' : 's'} · ${failures} failure${failures === 1 ? '' : 's'}`\n\n const scenes: Scene[] = [\n {\n sceneType: 'title_card',\n title,\n narration: taskEvent ? taskEvent.summary : 'The agent receives its task.',\n visual: taskEvent ? taskEvent.visual : { type: 'none' },\n durationMs: 3000,\n evidenceSpanIds: taskEvent ? taskEvent.evidenceSpanIds : [],\n },\n ...actionScenes,\n {\n sceneType: 'summary',\n title: failures > 0 ? 'Finished — with failures' : 'Finished',\n narration: summaryNarration,\n visual: { type: 'none' },\n durationMs: 4000,\n evidenceSpanIds: [],\n },\n ]\n return { title, totalMs: scenes.reduce((s, sc) => s + sc.durationMs, 0), scenes }\n}\n\n// ── Renderers (pure string out) ─────────────────────────────────────────────\n\nconst SCENE_ICON: Record<SceneType, string> = {\n title_card: '🎬',\n prompt: '💬',\n reply: '🤖',\n reasoning: '🧠',\n terminal: '⌨️',\n file: '📄',\n diff: '✏️',\n search: '🔎',\n browser: '🌐',\n computer: '🖥️',\n api: '🔌',\n tool: '🔧',\n eval: '⚖️',\n error: '❌',\n summary: '🏁',\n}\n\nfunction mmss(ms: number): string {\n const s = Math.round(ms / 1000)\n return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : `${s.slice(0, Math.max(0, max - 1))}…`\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\n/** A short modality tag for the markdown shot list. */\nfunction visualMarkdown(v: SceneVisual): string {\n switch (v.type) {\n case 'screenshot':\n return `🖼 screenshot (${truncate(v.src, 60)})`\n case 'browser':\n return `🌐 ${v.action ?? 'browser'}${v.url ? ` → ${v.url}` : ''}${v.screenshot ? ' [screenshot]' : ''}`\n case 'diff':\n return `\\n\\n\\`\\`\\`diff\\n${truncate(v.patch, 600)}\\n\\`\\`\\`${v.path ? `\\n_${v.path}_` : ''}`\n case 'code':\n return `\\n\\n\\`\\`\\`\\n${truncate(v.content, 500)}\\n\\`\\`\\`${v.path ? `\\n_${v.path}_` : ''}`\n case 'terminal':\n return `\\n\\n\\`\\`\\`sh\\n$ ${v.command}${v.output ? `\\n${truncate(v.output, 500)}` : ''}\\n\\`\\`\\``\n case 'api':\n return `🔌 ${v.method ?? 'CALL'} ${v.url}${v.status != null ? ` → ${v.status}` : ''}`\n case 'prose':\n return `\\n\\n> ${truncate(v.text, 400).replace(/\\n/g, '\\n> ')}`\n default:\n return ''\n }\n}\n\n/** Build the (escaped, structured) HTML for a scene's visual. We never inject\n * raw agent markup — every text part is escaped; only image `src` (data/http)\n * is passed through as an attribute. */\nfunction visualHtml(v: SceneVisual): string {\n switch (v.type) {\n case 'screenshot':\n return `<img class=\"shot\" src=\"${escapeHtml(v.src)}\" alt=\"screenshot\" loading=\"lazy\"/>`\n case 'browser': {\n const bar = `<div class=\"urlbar\">${escapeHtml(v.url ?? v.action ?? 'browser')}</div>`\n const body = isImageSrc(v.screenshot)\n ? `<img class=\"shot\" src=\"${escapeHtml(v.screenshot)}\" alt=\"page\" loading=\"lazy\"/>`\n : `<div class=\"browser-action\">${escapeHtml(v.action ?? 'navigated')}</div>`\n return `<div class=\"browser\">${bar}${body}</div>`\n }\n case 'diff': {\n const lines = v.patch.split('\\n').map((l) => {\n const cls = l.startsWith('+') ? 'add' : l.startsWith('-') ? 'del' : ''\n return `<span class=\"${cls}\">${escapeHtml(l)}</span>`\n })\n return `${v.path ? `<div class=\"pathhdr\">${escapeHtml(v.path)}</div>` : ''}<pre class=\"diff\">${lines.join('\\n')}</pre>`\n }\n case 'code':\n return `${v.path ? `<div class=\"pathhdr\">${escapeHtml(v.path)}</div>` : ''}<pre class=\"code\">${escapeHtml(v.content)}</pre>`\n case 'terminal':\n return `<pre class=\"term\"><span class=\"cmd\">$ ${escapeHtml(v.command)}</span>${v.output ? `\\n${escapeHtml(v.output)}` : ''}</pre>`\n case 'api': {\n const badge = `<span class=\"method\">${escapeHtml(v.method ?? 'CALL')}</span> <span class=\"url\">${escapeHtml(v.url)}</span>${v.status != null ? ` <span class=\"status s${Math.floor(v.status / 100)}\">${v.status}</span>` : ''}`\n const req = v.request\n ? `<div class=\"kv\"><b>req</b><pre>${escapeHtml(v.request)}</pre></div>`\n : ''\n const res = v.response\n ? `<div class=\"kv\"><b>res</b><pre>${escapeHtml(v.response)}</pre></div>`\n : ''\n return `<div class=\"api\"><div class=\"apihdr\">${badge}</div>${req}${res}</div>`\n }\n case 'prose':\n return `<div class=\"prose\">${escapeHtml(v.text)}</div>`\n default:\n return ''\n }\n}\n\n/** Render the storyboard as a Markdown timeline — the human-readable shot list. */\nexport function renderStoryboardMarkdown(storyboard: Storyboard): string {\n const out: string[] = [\n `# 🎬 ${storyboard.title}`,\n '',\n `_${storyboard.scenes.length} scenes · ${mmss(storyboard.totalMs)} replay_`,\n '',\n ]\n let elapsed = 0\n for (const sc of storyboard.scenes) {\n out.push(`### [${mmss(elapsed)}] ${SCENE_ICON[sc.sceneType]} ${sc.title}`)\n out.push('')\n out.push(sc.narration)\n const vm = visualMarkdown(sc.visual)\n if (vm) out.push(vm)\n if (sc.evidenceSpanIds.length > 0) {\n out.push('')\n out.push(`_evidence: ${sc.evidenceSpanIds.length} span(s)_`)\n }\n out.push('')\n elapsed += sc.durationMs\n }\n return out.join('\\n')\n}\n\nexport interface HtmlRenderOptions {\n /** Document title + on-page heading. Defaults to the storyboard title. */\n title?: string\n}\n\n/**\n * Render the storyboard as a self-contained, auto-playing HTML replay — one\n * shareable file, no build step, no external assets. Each scene animates in\n * for its duration and SHOWS its modality (screenshot / diff / terminal / API\n * / browser / prose); controls let a viewer scrub. This is the \"free clip\" a\n * run produces; an MP4 is the same storyboard fed to Remotion in a consumer.\n */\nexport function renderStoryboardHtml(storyboard: Storyboard, opts: HtmlRenderOptions = {}): string {\n const docTitle = opts.title ?? storyboard.title\n // Pre-render each scene's visual to safe HTML here (testable, escaped); the\n // player just sets innerHTML. `<` is escaped in the JSON so a payload can't\n // break out of the <script> tag.\n const scenesJson = JSON.stringify(\n storyboard.scenes.map((s) => ({\n icon: SCENE_ICON[s.sceneType],\n type: s.sceneType,\n title: s.title,\n narration: s.narration,\n durationMs: s.durationMs,\n evidence: s.evidenceSpanIds.length,\n visual: visualHtml(s.visual),\n })),\n ).replace(/</g, '\\\\u003c')\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>${escapeHtml(docTitle)}</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body { margin: 0; font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;\n background: #0b0f17; color: #e6edf3; display: flex; min-height: 100vh; align-items: center; justify-content: center; }\n .stage { width: min(900px, 95vw); }\n h1 { font-size: 1.1rem; font-weight: 600; color: #9aa7b5; margin: 0 0 14px; letter-spacing: .02em; }\n .card { background: #111824; border: 1px solid #1e2a3a; border-radius: 14px; padding: 24px 28px;\n min-height: 300px; box-shadow: 0 12px 40px rgba(0,0,0,.45); position: relative; overflow: hidden; }\n .card::before { content: \"\"; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--accent, #3b82f6); }\n .head { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }\n .icon { font-size: 2rem; line-height: 1; }\n .title { font-size: 1.3rem; font-weight: 650; }\n .narration { color: #9fb0c0; font-size: .95rem; margin-bottom: 14px; }\n .visual { margin-top: 6px; }\n .visual img.shot { max-width: 100%; max-height: 360px; border-radius: 8px; border: 1px solid #1e2a3a; display: block; }\n .visual pre { background: #0a0e15; border: 1px solid #1e2a3a; border-radius: 8px; padding: 12px 14px;\n overflow: auto; max-height: 340px; font: 13px/1.5 ui-monospace, \"SF Mono\", Menlo, monospace; white-space: pre-wrap; word-break: break-word; }\n .pathhdr { font: 12px ui-monospace, monospace; color: #7d8da0; margin-bottom: 4px; }\n .diff .add { color: #56d364; } .diff .del { color: #f85149; }\n .term .cmd { color: #eab308; }\n .browser { border: 1px solid #1e2a3a; border-radius: 8px; overflow: hidden; }\n .urlbar { background: #0a0e15; padding: 7px 12px; font: 12px ui-monospace, monospace; color: #8b98a6; border-bottom: 1px solid #1e2a3a; }\n .browser-action { padding: 28px; color: #9fb0c0; text-align: center; }\n .api .apihdr { font: 13px ui-monospace, monospace; margin-bottom: 8px; }\n .method { background: #1b2636; padding: 2px 8px; border-radius: 5px; color: #79c0ff; }\n .status.s2 { color: #56d364; } .status.s4, .status.s5 { color: #f85149; }\n .kv { margin-top: 6px; } .kv b { color: #7d8da0; font-weight: 600; font-size: 12px; }\n .prose { color: #c9d4e0; font-size: 1rem; white-space: pre-wrap; max-height: 340px; overflow: auto; }\n .type-error { --accent: #ef4444; } .type-diff { --accent: #22c55e; } .type-terminal { --accent: #eab308; }\n .type-browser { --accent: #06b6d4; } .type-computer { --accent: #14b8a6; } .type-api { --accent: #79c0ff; }\n .type-summary { --accent: #a855f7; } .type-title_card { --accent: #3b82f6; }\n .type-prompt { --accent: #f59e0b; } .type-reply { --accent: #10b981; }\n .scene-enter { animation: pop .45s cubic-bezier(.2,.7,.3,1.2); }\n @keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.985); } to { opacity: 1; transform: none; } }\n .bar { height: 4px; background: #1e2a3a; border-radius: 4px; margin-top: 14px; overflow: hidden; }\n .bar > i { display: block; height: 100%; width: 0; background: var(--accent, #3b82f6); transition: width .1s linear; }\n .controls { display: flex; gap: 10px; align-items: center; margin-top: 14px; color: #8b98a6; font-size: .85rem; }\n button { background: #1b2636; color: #e6edf3; border: 1px solid #2a3a4f; border-radius: 8px; padding: 6px 12px; cursor: pointer; font: inherit; }\n button:hover { background: #243349; }\n .dots { display: flex; gap: 5px; margin-left: auto; flex-wrap: wrap; }\n .dot { width: 7px; height: 7px; border-radius: 50%; background: #2a3a4f; cursor: pointer; }\n .dot.on { background: var(--accent, #3b82f6); }\n</style>\n</head>\n<body>\n<div class=\"stage\">\n <h1>🎬 ${escapeHtml(docTitle)}</h1>\n <div class=\"card\" id=\"card\">\n <div class=\"head\"><span class=\"icon\" id=\"icon\"></span><span class=\"title\" id=\"title\"></span></div>\n <div class=\"narration\" id=\"narration\"></div>\n <div class=\"visual\" id=\"visual\"></div>\n <div class=\"bar\"><i id=\"fill\"></i></div>\n </div>\n <div class=\"controls\">\n <button id=\"playPause\">⏸ Pause</button>\n <button id=\"prev\">◀ Prev</button>\n <button id=\"next\">Next ▶</button>\n <span id=\"counter\"></span>\n <span class=\"dots\" id=\"dots\"></span>\n </div>\n</div>\n<script>\n const SCENES = ${scenesJson};\n let i = 0, playing = true, raf = 0, start = 0;\n const card = document.getElementById('card'), counter = document.getElementById('counter'), dotsEl = document.getElementById('dots');\n SCENES.forEach((_, k) => { const d = document.createElement('span'); d.className = 'dot'; d.onclick = () => go(k); dotsEl.appendChild(d); });\n function paint() {\n const s = SCENES[i];\n card.className = 'card scene-enter type-' + s.type;\n document.getElementById('icon').textContent = s.icon;\n document.getElementById('title').textContent = s.title;\n document.getElementById('narration').textContent = s.narration;\n document.getElementById('visual').innerHTML = s.visual || '';\n counter.textContent = (i + 1) + ' / ' + SCENES.length;\n [...dotsEl.children].forEach((d, k) => d.className = 'dot' + (k === i ? ' on' : ''));\n void card.offsetWidth;\n }\n function tick(t) {\n if (!start) start = t;\n const s = SCENES[i], p = Math.min(1, (t - start) / s.durationMs);\n document.getElementById('fill').style.width = (p * 100) + '%';\n if (p >= 1) { if (i < SCENES.length - 1) { go(i + 1); } else { playing = false; updateBtn(); return; } }\n if (playing) raf = requestAnimationFrame(tick);\n }\n function go(n) { i = Math.max(0, Math.min(SCENES.length - 1, n)); start = 0; cancelAnimationFrame(raf); paint(); if (playing) raf = requestAnimationFrame(tick); }\n function updateBtn() { document.getElementById('playPause').textContent = playing ? '⏸ Pause' : '▶ Play'; }\n document.getElementById('playPause').onclick = () => { playing = !playing; updateBtn(); start = 0; if (playing) raf = requestAnimationFrame(tick); else cancelAnimationFrame(raf); };\n document.getElementById('next').onclick = () => go(i + 1);\n document.getElementById('prev').onclick = () => go(i - 1);\n paint(); raf = requestAnimationFrame(tick);\n</script>\n</body>\n</html>\n`\n}\n\n// The \"watch the agent write code\" keystroke animation + the CodeEdit IR a\n// Remotion consumer reads. Composes with this pipeline: a 'diff' scene shows an\n// inline card here; renderCodeAnimationHtml types the whole file out there.\nexport * from './code-edit'\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-eval",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Substrate for self-improving agents: traces, verifiable rewards, preferences, GEPA / reflective mutation, auto-research, replay, sequential anytime-valid stats, and release gates.",
5
5
  "homepage": "https://github.com/tangle-network/agent-eval#readme",
6
6
  "repository": {