@tangle-network/sandbox-ui 2.0.21 → 2.0.22

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/record.js ADDED
@@ -0,0 +1,2329 @@
1
+ import { a as fmt, c as modelIdentity, d as recordFlowTrace, f as roleOf, h as utcTime, i as eventSource, m as toolKey, n as Conversation, o as indexRecord, p as textOf, r as eventMatches, s as interval, t as AgentTree, u as ms } from "./AgentTree-KO1TJXBq.js";
2
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@tangle-network/ui/primitives";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { createContext, memo, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
5
+ import { useColorMode } from "@tangle-network/charts/react/agent-graph/presentation";
6
+ import { runStatus, statusMeta } from "@tangle-network/ui/run";
7
+ import { FigureView, FlowWaterfall } from "@tangle-network/charts/react";
8
+ import { OUTCOME_ORDER, timeSeries } from "@tangle-network/charts";
9
+ import Markdown from "react-markdown";
10
+ import remarkGfm from "remark-gfm";
11
+ import remarkMath from "remark-math";
12
+ import rehypeKatex from "rehype-katex";
13
+ import { parseReportOptions, reportToLatex } from "@drewstone/agent-record/report";
14
+ import { Controls, Handle, MarkerType, Position, ReactFlow } from "@xyflow/react";
15
+ import { NODE_H, NODE_W, PER_LANE, recordWorkGraphModel } from "@drewstone/agent-record/record";
16
+ // src/record/StructuredContent.tsx
17
+ /** Decode display wrappers only. Original text remains available separately. */
18
+ function displayValue(text) {
19
+ try {
20
+ return JSON.parse(text);
21
+ } catch {
22
+ return text;
23
+ }
24
+ }
25
+ function MessageText({ text, resolveHref }) {
26
+ return /* @__PURE__ */ jsx("div", {
27
+ className: "activity-prose",
28
+ children: /* @__PURE__ */ jsx(Markdown, {
29
+ skipHtml: true,
30
+ remarkPlugins: [remarkGfm],
31
+ components: {
32
+ a: ({ href, children }) => {
33
+ const target = /^https?:\/\//i.test(href ?? "") ? href : resolveHref?.(href ?? "");
34
+ return target && (/^https?:\/\//i.test(target) || /^\/(?!\/)/.test(target)) ? /* @__PURE__ */ jsx("a", {
35
+ href: target,
36
+ target: "_blank",
37
+ rel: "noreferrer",
38
+ children
39
+ }) : /* @__PURE__ */ jsx("span", { children });
40
+ },
41
+ img: ({ alt }) => /* @__PURE__ */ jsx("span", { children: alt || "Image omitted" }),
42
+ table: ({ children }) => /* @__PURE__ */ jsx("div", {
43
+ className: "rr-table-scroll",
44
+ children: /* @__PURE__ */ jsx("table", { children })
45
+ })
46
+ },
47
+ children: text
48
+ })
49
+ });
50
+ }
51
+ function Value({ value, depth = 0, field }) {
52
+ const [expanded, setExpanded] = useState(false);
53
+ if (value === null || value === void 0) return /* @__PURE__ */ jsx("span", {
54
+ className: "small",
55
+ children: "Not supplied"
56
+ });
57
+ if (typeof value === "string") {
58
+ if (field && /^(command|cmd|code|script|stdout|stderr)$/i.test(field)) return /* @__PURE__ */ jsx("pre", {
59
+ className: "activity-code",
60
+ children: /* @__PURE__ */ jsx("code", { children: value })
61
+ });
62
+ const decoded = depth < 3 ? displayValue(value) : value;
63
+ return typeof decoded === "string" ? /* @__PURE__ */ jsx(MessageText, { text: value }) : /* @__PURE__ */ jsx(Value, {
64
+ value: decoded,
65
+ depth: depth + 1
66
+ });
67
+ }
68
+ if (typeof value !== "object") return /* @__PURE__ */ jsx("span", { children: String(value) });
69
+ if (Array.isArray(value)) {
70
+ const shown = expanded ? value : value.slice(0, 12);
71
+ return /* @__PURE__ */ jsxs("div", {
72
+ className: "structured-items",
73
+ children: [shown.map((item, i) => /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Value, {
74
+ value: item,
75
+ depth: depth + 1
76
+ }) }, i)), value.length > shown.length && /* @__PURE__ */ jsxs("button", {
77
+ className: "ui-button",
78
+ onClick: () => setExpanded(true),
79
+ children: [
80
+ "Show all ",
81
+ value.length,
82
+ " items"
83
+ ]
84
+ })]
85
+ });
86
+ }
87
+ const data = value;
88
+ if (data.type === "text" && typeof data.text === "string") return /* @__PURE__ */ jsx(Value, {
89
+ value: data.text,
90
+ depth: depth + 1
91
+ });
92
+ const entries = Object.entries(data);
93
+ const shown = expanded ? entries : entries.slice(0, 16);
94
+ if (depth > 4) return /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", { children: "Nested data" }), /* @__PURE__ */ jsx("pre", { children: JSON.stringify(value, null, 2) })] });
95
+ return /* @__PURE__ */ jsxs("dl", {
96
+ className: "structured-fields",
97
+ children: [shown.map(([key, item]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: key.replace(/([a-z])([A-Z])/g, "$1 $2").replaceAll("_", " ") }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(Value, {
98
+ value: item,
99
+ depth: depth + 1,
100
+ field: key
101
+ }) })] }, key)), entries.length > shown.length && /* @__PURE__ */ jsxs("button", {
102
+ className: "ui-button",
103
+ onClick: () => setExpanded(true),
104
+ children: [
105
+ "Show all ",
106
+ entries.length,
107
+ " fields"
108
+ ]
109
+ })]
110
+ });
111
+ }
112
+ function StructuredContent({ text, rawLabel = "Raw data" }) {
113
+ const value = displayValue(text);
114
+ return /* @__PURE__ */ jsxs("div", {
115
+ className: "structured-content",
116
+ children: [/* @__PURE__ */ jsx(Value, { value }), typeof value !== "string" && /* @__PURE__ */ jsxs("details", {
117
+ className: "raw-data",
118
+ children: [/* @__PURE__ */ jsx("summary", { children: rawLabel }), /* @__PURE__ */ jsx("pre", { children: text })]
119
+ })]
120
+ });
121
+ }
122
+ // src/record/RecordUsage.tsx
123
+ const categories = [
124
+ "coordination",
125
+ "compute",
126
+ "computation",
127
+ "verification",
128
+ "literature",
129
+ "writing",
130
+ "setup",
131
+ "infrastructure",
132
+ "waiting",
133
+ "reasoning",
134
+ "lifecycle",
135
+ "other"
136
+ ];
137
+ const channels = [
138
+ "input",
139
+ "output",
140
+ "cacheRead",
141
+ "cacheWrite"
142
+ ];
143
+ const channelLabels = {
144
+ input: "Input",
145
+ output: "Output",
146
+ cacheRead: "Cache read",
147
+ cacheWrite: "Cache write"
148
+ };
149
+ function RecordUsage({ index, cutoff, inspect, navigate, actor, metric, axis }) {
150
+ const [hidden, setHidden] = useState(() => /* @__PURE__ */ new Set());
151
+ const visible = useMemo(() => channels.filter((channel) => !hidden.has(channel)), [hidden]);
152
+ const measured = useMemo(() => {
153
+ const points = [];
154
+ const totals = {
155
+ input: 0,
156
+ output: 0,
157
+ cacheRead: 0,
158
+ cacheWrite: 0
159
+ };
160
+ const counts = {
161
+ input: 0,
162
+ output: 0,
163
+ cacheRead: 0,
164
+ cacheWrite: 0
165
+ };
166
+ let incomplete = 0, ambiguous = 0, invalidTiming = 0;
167
+ const chronological = index.byActor.get(actor) ?? [];
168
+ const order = index.recordedOrder;
169
+ const events = axis === "order" ? [...chronological].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : chronological;
170
+ for (const event of events) {
171
+ if (ms(event.at) > cutoff) continue;
172
+ if (metric === "tool-time") {
173
+ if (event.detail.toolCallId === void 0) continue;
174
+ const calls = index.calls.get(toolKey(event.node, event.detail.toolCallId)) ?? [];
175
+ if (calls.length !== 1 || index.results.get(toolKey(event.node, event.detail.toolCallId))?.length !== 1) {
176
+ ambiguous++;
177
+ continue;
178
+ }
179
+ const call = calls[0].event;
180
+ if (ms(call.at) > cutoff || ms(event.at) < ms(call.at)) {
181
+ invalidTiming++;
182
+ continue;
183
+ }
184
+ points.push({
185
+ event,
186
+ at: ms(event.at),
187
+ values: {},
188
+ elapsed: (ms(event.at) - ms(call.at)) / 1e3
189
+ });
190
+ continue;
191
+ }
192
+ const usage = event.detail.usage;
193
+ if (!usage) continue;
194
+ const scoped = event.detail.usageScope !== void 0;
195
+ if (metric !== "cumulative" && scoped) continue;
196
+ if (channels.some((channel) => usage[channel] === void 0)) incomplete++;
197
+ const values = {};
198
+ for (const channel of channels) {
199
+ const value = usage[channel];
200
+ if (value === void 0) continue;
201
+ if (!scoped) {
202
+ totals[channel] += value;
203
+ counts[channel]++;
204
+ }
205
+ values[channel] = metric === "cumulative" ? scoped ? value : totals[channel] : value;
206
+ }
207
+ if (Object.keys(values).length) points.push({
208
+ event,
209
+ at: ms(event.at),
210
+ values,
211
+ scoped
212
+ });
213
+ }
214
+ return {
215
+ points,
216
+ totals,
217
+ counts,
218
+ incomplete,
219
+ ambiguous,
220
+ invalidTiming
221
+ };
222
+ }, [
223
+ index,
224
+ cutoff,
225
+ actor,
226
+ metric,
227
+ axis
228
+ ]);
229
+ const { points, totals, counts } = measured;
230
+ const plotted = useMemo(() => {
231
+ const sourceEvents = [];
232
+ const position = new Map(points.map((point, i) => [point, i + 1]));
233
+ const series = (id, label, colorIndex, scoped, channel) => ({
234
+ id,
235
+ label,
236
+ colorIndex,
237
+ secondary: scoped,
238
+ points: points.filter((point) => !!point.scoped === scoped).map((point) => {
239
+ const y = metric === "tool-time" ? point.elapsed : point.values[channel] ?? null;
240
+ if (y !== null) sourceEvents.push(point.event);
241
+ return {
242
+ x: axis === "time" ? point.at : position.get(point),
243
+ y,
244
+ ...point.event.detail.isError ? { marker: "triangle" } : {},
245
+ annotation: [
246
+ point.event.label,
247
+ point.event.detail.isError ? "The tool reported an error." : "",
248
+ scoped ? "Metered window or execution-to-date; not a per-response measurement." : ""
249
+ ].filter(Boolean).join(" ") || void 0
250
+ };
251
+ })
252
+ });
253
+ return {
254
+ rows: metric === "tool-time" ? [series("tool-return", "Observed call → result interval", 0, false)] : visible.flatMap((channel) => [series(channel, channelLabels[channel], channels.indexOf(channel), false, channel), ...points.some((point) => point.scoped) ? [series(`scoped:${channel}`, `${channelLabels[channel]} · metered windows`, channels.indexOf(channel), true, channel)] : []]),
255
+ sourceEvents
256
+ };
257
+ }, [
258
+ points,
259
+ visible,
260
+ metric,
261
+ axis
262
+ ]);
263
+ const figure = useMemo(() => (width) => timeSeries(plotted.rows, {
264
+ id: "record-usage",
265
+ width,
266
+ selectable: true,
267
+ x: {
268
+ label: axis === "time" ? "Recorded elapsed time" : "Recorded event order",
269
+ domain: axis === "time" ? [index.start, Math.max(index.start + 1, cutoff)] : [1, Math.max(2, points.length)]
270
+ },
271
+ y: {
272
+ label: metric === "tool-time" ? "Observed call → result interval" : metric === "cumulative" ? "Cumulative measured tokens" : "Measured tokens per response",
273
+ unit: metric === "tool-time" ? "seconds" : "tokens",
274
+ domain: [0, Math.max(1, ...plotted.rows.flatMap((row) => row.points.map((point) => point.y ?? 0)))]
275
+ },
276
+ formatX: axis === "time" ? (value) => interval(value - index.start) : String,
277
+ formatY: metric === "tool-time" ? (value) => value.toFixed(3) : fmt
278
+ }), [
279
+ plotted,
280
+ axis,
281
+ metric,
282
+ index.start,
283
+ cutoff,
284
+ points.length
285
+ ]);
286
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
287
+ /* @__PURE__ */ jsx("div", {
288
+ "data-tokens": true,
289
+ onKeyDown: (event) => {
290
+ if ((event.key === "ArrowLeft" || event.key === "ArrowRight") && event.target instanceof Element && event.target.closest("svg")) {
291
+ event.preventDefault();
292
+ navigate(event.key === "ArrowLeft" ? -1 : 1);
293
+ }
294
+ },
295
+ children: points.length ? metric !== "tool-time" && !visible.length ? /* @__PURE__ */ jsx("p", {
296
+ className: "chat-empty",
297
+ children: "All token channels are hidden. Select a channel to show its recorded values."
298
+ }) : /* @__PURE__ */ jsx(FigureView, {
299
+ figure,
300
+ onPointSelect: (point) => {
301
+ const event = plotted.sourceEvents[point];
302
+ if (event) inspect(event, true);
303
+ }
304
+ }) : /* @__PURE__ */ jsx("p", {
305
+ className: "chat-empty",
306
+ children: metric === "tool-time" ? "No matched tool timing at this time" : "No measured usage at this time"
307
+ })
308
+ }),
309
+ metric !== "tool-time" && /* @__PURE__ */ jsxs("div", {
310
+ className: "usage-legend",
311
+ children: [channels.map((channel) => /* @__PURE__ */ jsx("button", {
312
+ type: "button",
313
+ className: "legend-toggle",
314
+ "aria-pressed": !hidden.has(channel),
315
+ title: hidden.has(channel) ? `Show ${channelLabels[channel]}` : `Hide ${channelLabels[channel]}`,
316
+ onClick: () => setHidden((current) => {
317
+ const next = new Set(current);
318
+ if (next.has(channel)) next.delete(channel);
319
+ else next.add(channel);
320
+ return next;
321
+ }),
322
+ children: channelLabels[channel]
323
+ }, channel)), points.some((point) => point.scoped) && /* @__PURE__ */ jsx("span", {
324
+ className: "scoped-key",
325
+ children: "metered windows · execution-to-date"
326
+ })]
327
+ }),
328
+ /* @__PURE__ */ jsx("p", {
329
+ className: "small",
330
+ "data-token-caption": true,
331
+ children: metric === "tool-time" ? `Observed tool return time, not model latency.${measured.ambiguous || measured.invalidTiming ? " Unmatched, ambiguous and out-of-order results are omitted." : ""}` : `${channels.map((channel) => `${channelLabels[channel]} ${counts[channel] ? fmt(totals[channel]) : "unknown"}`).join(" · ")}.${measured.incomplete ? " Some responses lack counters, so these totals are lower bounds." : ""} Context repeated across responses is counted each time; tokens are not billed cost.`
332
+ })
333
+ ] });
334
+ }
335
+ // src/record/useDownload.ts
336
+ function useDownload() {
337
+ const pending = useRef(/* @__PURE__ */ new Map());
338
+ useEffect(() => {
339
+ const urls = pending.current;
340
+ return () => {
341
+ for (const [url, timer] of urls) {
342
+ window.clearTimeout(timer);
343
+ URL.revokeObjectURL(url);
344
+ }
345
+ urls.clear();
346
+ };
347
+ }, []);
348
+ return (content, name, type) => {
349
+ const url = URL.createObjectURL(new Blob([content], { type }));
350
+ const link = document.createElement("a");
351
+ link.href = url;
352
+ link.download = name.replace(/[^a-zA-Z0-9._-]/g, "_");
353
+ link.click();
354
+ pending.current.set(url, window.setTimeout(() => {
355
+ URL.revokeObjectURL(url);
356
+ pending.current.delete(url);
357
+ }, 1e3));
358
+ };
359
+ }
360
+ // src/record/AgentRecord.tsx
361
+ function Select$1({ label, value, onChange, children, field }) {
362
+ return /* @__PURE__ */ jsxs("label", {
363
+ className: "ui-field ar-select",
364
+ children: [/* @__PURE__ */ jsx("span", {
365
+ className: "ui-label",
366
+ children: label
367
+ }), /* @__PURE__ */ jsx("select", {
368
+ value,
369
+ onChange: (event) => onChange(event.target.value),
370
+ [`data-${field}`]: "",
371
+ children
372
+ })]
373
+ });
374
+ }
375
+ /** Normalized retained data only. Producers retain parsing and publication authority. */
376
+ function AgentRecord({ records, selection, defaultSelection, onSelectionChange, theme = "auto", className = "", behaviors }) {
377
+ const [own, setOwn] = useState(defaultSelection);
378
+ const chosen = selection ?? own;
379
+ const record = records.find((item) => item.runId === chosen?.runId) ?? records[0];
380
+ const change = useCallback((next) => {
381
+ if (selection === void 0) setOwn(next);
382
+ onSelectionChange?.(next);
383
+ }, [selection, onSelectionChange]);
384
+ return /* @__PURE__ */ jsx("div", {
385
+ className: `agent-record ${className}`,
386
+ "data-theme": theme === "auto" ? void 0 : theme,
387
+ children: record ? /* @__PURE__ */ jsx(RecordView, {
388
+ records,
389
+ record,
390
+ selection: chosen?.runId === record.runId ? chosen : { runId: record.runId },
391
+ onChange: change,
392
+ behaviors
393
+ }, record.runId) : /* @__PURE__ */ jsx("p", {
394
+ className: "chat-empty",
395
+ children: "No records supplied."
396
+ })
397
+ });
398
+ }
399
+ /** One retained record keeps its selection across timeline, transcript, source and measured usage. */
400
+ function RecordView({ records, record, selection, onChange, behaviors }) {
401
+ const index = useMemo(() => indexRecord(record), [record]);
402
+ const trace = useMemo(() => recordFlowTrace(record), [record]);
403
+ const [category, setCategory] = useState("all"), [search, setSearch] = useState("");
404
+ const [metric, setMetric] = useState("cumulative"), [axis, setAxis] = useState("time");
405
+ const download = useDownload();
406
+ const explicit = selection.eventId ? index.byEvent.get(selection.eventId) : void 0;
407
+ const requested = selection.nodeId ? index.canonical(selection.nodeId) : void 0;
408
+ const actor = explicit ? index.canonical(explicit.node) : requested && index.nodes.has(requested) ? requested : index.actors[0]?.id ?? "";
409
+ const event = explicit ?? index.byActor.get(actor)?.[0], node = index.nodes.get(actor);
410
+ const selectedAt = selection.at ? ms(selection.at) : index.end;
411
+ const cutoff = Number.isFinite(selectedAt) ? Math.max(index.start, Math.min(index.end, selectedAt)) : index.end;
412
+ const query = search.trim().toLowerCase(), view = selection.view ?? "chat";
413
+ const visible = useMemo(() => index.events.filter((item) => eventMatches(item, index, query, category)), [
414
+ index,
415
+ query,
416
+ category
417
+ ]);
418
+ const options = [.../* @__PURE__ */ new Set([...categories, ...record.events.map((item) => item.category)])].filter((item) => item !== "all" && item !== "findings");
419
+ const marks = (behaviors?.find((item) => item.runId === record.runId)?.verdicts ?? []).filter((item) => item.verdict === "yes").flatMap((item) => (item.evidence ?? []).flatMap((evidence) => {
420
+ const event = index.byEvent.get(evidence.eventId);
421
+ return event ? [{
422
+ ...item,
423
+ event,
424
+ quote: evidence.quote
425
+ }] : [];
426
+ }));
427
+ const update = useCallback((patch) => onChange({
428
+ ...selection,
429
+ runId: record.runId,
430
+ nodeId: actor,
431
+ eventId: event?.id,
432
+ ...patch
433
+ }), [
434
+ selection,
435
+ record.runId,
436
+ actor,
437
+ event?.id,
438
+ onChange
439
+ ]);
440
+ const inspect = (item, source = false) => onChange({
441
+ runId: record.runId,
442
+ nodeId: index.canonical(item.node),
443
+ eventId: item.id,
444
+ at: item.at,
445
+ view: source ? "source" : "chat"
446
+ });
447
+ const selectActor = (id) => {
448
+ const first = index.byActor.get(id)?.find((item) => item.detail.role || item.detail.publicText || item.detail.publicToolCalls?.length) ?? index.byActor.get(id)?.[0];
449
+ onChange({
450
+ runId: record.runId,
451
+ nodeId: id,
452
+ eventId: first?.id,
453
+ view: "chat",
454
+ at: first && ms(first.at) > cutoff ? first.at : selection.at
455
+ });
456
+ };
457
+ const selectSpan = (span) => {
458
+ const item = index.byEvent.get(typeof span.meta?.eventId === "string" ? span.meta.eventId : span.id ?? "");
459
+ if (item) inspect(item);
460
+ else if (span.id !== void 0 && index.nodes.has(span.id)) selectActor(span.id);
461
+ };
462
+ const navigate = (direction) => {
463
+ if (!visible.length) return;
464
+ const at = visible.findIndex((item) => item.id === event?.id);
465
+ inspect(visible[at < 0 ? direction > 0 ? 0 : visible.length - 1 : (at + direction + visible.length) % visible.length]);
466
+ };
467
+ const position = visible.findIndex((item) => item.id === event?.id);
468
+ return /* @__PURE__ */ jsxs("div", {
469
+ className: "ar-view",
470
+ children: [
471
+ /* @__PURE__ */ jsxs("div", {
472
+ className: "controls",
473
+ children: [
474
+ records.length > 1 && /* @__PURE__ */ jsx(Select$1, {
475
+ label: "Run",
476
+ field: "run",
477
+ value: record.runId,
478
+ onChange: (runId) => onChange({ runId }),
479
+ children: records.map((item) => /* @__PURE__ */ jsx("option", {
480
+ value: item.runId,
481
+ children: item.title
482
+ }, item.runId))
483
+ }),
484
+ /* @__PURE__ */ jsxs(Select$1, {
485
+ label: "Activity type",
486
+ field: "category",
487
+ value: category,
488
+ onChange: setCategory,
489
+ children: [
490
+ /* @__PURE__ */ jsx("option", {
491
+ value: "all",
492
+ children: "All activity"
493
+ }),
494
+ /* @__PURE__ */ jsx("option", {
495
+ value: "findings",
496
+ children: "Recorded findings"
497
+ }),
498
+ options.map((item) => /* @__PURE__ */ jsx("option", {
499
+ value: item,
500
+ children: item
501
+ }, item))
502
+ ]
503
+ }),
504
+ /* @__PURE__ */ jsxs("label", {
505
+ className: "ui-field",
506
+ children: [/* @__PURE__ */ jsx("span", {
507
+ className: "ui-label",
508
+ children: "Search all agents"
509
+ }), /* @__PURE__ */ jsx("input", {
510
+ "data-search": true,
511
+ type: "search",
512
+ value: search,
513
+ onChange: (event) => setSearch(event.target.value),
514
+ placeholder: "Message, tool, agent…"
515
+ })]
516
+ })
517
+ ]
518
+ }),
519
+ /* @__PURE__ */ jsxs("details", {
520
+ className: "activity-replay",
521
+ open: true,
522
+ children: [
523
+ /* @__PURE__ */ jsx("summary", { children: "Timeline and replay" }),
524
+ /* @__PURE__ */ jsx(FlowWaterfall, {
525
+ trace,
526
+ live: false,
527
+ label: "Recorded activity",
528
+ defaultView: "agents",
529
+ selectedId: actor,
530
+ position: selection.at ? cutoff - index.start : null,
531
+ onTimeChange: (time) => {
532
+ const at = time === null ? void 0 : new Date(index.start + time).toISOString();
533
+ if (at !== selection.at) update({ at });
534
+ },
535
+ onSelect: selectSpan,
536
+ query: search,
537
+ onQueryChange: setSearch,
538
+ matchSpan: (span) => {
539
+ const item = index.byEvent.get(typeof span.meta?.eventId === "string" ? span.meta.eventId : span.id ?? "");
540
+ return item ? eventMatches(item, index, query, category) : !query && category === "all";
541
+ },
542
+ markers: marks.map((mark) => ({
543
+ at: ms(mark.event.at) - index.start,
544
+ label: `${mark.behavior} · ${Math.round(mark.confidence * 100)}%`
545
+ }))
546
+ }),
547
+ marks.length > 0 && /* @__PURE__ */ jsx("ul", {
548
+ "aria-label": "Recorded behavior verdicts",
549
+ children: marks.map((mark, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
550
+ type: "button",
551
+ onClick: () => inspect(mark.event, true),
552
+ children: [
553
+ mark.behavior,
554
+ " · ",
555
+ Math.round(mark.confidence * 100),
556
+ "%",
557
+ mark.quote ? ` · ${mark.quote}` : ""
558
+ ]
559
+ }) }, i))
560
+ })
561
+ ]
562
+ }),
563
+ query && /* @__PURE__ */ jsxs("section", {
564
+ className: "activity-search-results",
565
+ "aria-label": "Matching activity",
566
+ children: [
567
+ /* @__PURE__ */ jsxs("p", {
568
+ role: "status",
569
+ children: [visible.length, " matching events"]
570
+ }),
571
+ visible.slice(0, 100).map((item) => /* @__PURE__ */ jsxs("button", {
572
+ onClick: () => {
573
+ setSearch("");
574
+ inspect(item);
575
+ },
576
+ children: [
577
+ /* @__PURE__ */ jsx("span", { children: index.nodes.get(index.canonical(item.node))?.label ?? item.node }),
578
+ /* @__PURE__ */ jsx("time", { children: utcTime(item.at) }),
579
+ /* @__PURE__ */ jsx("strong", { children: item.label }),
580
+ /* @__PURE__ */ jsx("small", { children: (textOf(item) || item.detail.publicToolCalls?.map((call) => `${call.name} ${call.input}`).join(" ") || "Recorded event").replace(/\s+/g, " ").slice(0, 240) })
581
+ ]
582
+ }, item.id)),
583
+ visible.length > 100 && /* @__PURE__ */ jsx("p", {
584
+ className: "small",
585
+ children: "Showing the first 100 matches. Narrow the search to find a particular event."
586
+ }),
587
+ !visible.length && /* @__PURE__ */ jsx("p", {
588
+ className: "chat-empty",
589
+ children: "No retained activity matches. Missing captures are not searched."
590
+ })
591
+ ]
592
+ }),
593
+ /* @__PURE__ */ jsxs("div", {
594
+ className: "workspace flat-activity",
595
+ hidden: !!query,
596
+ children: [/* @__PURE__ */ jsxs("details", {
597
+ className: "topology",
598
+ children: [
599
+ /* @__PURE__ */ jsxs("summary", { children: ["Agents ", /* @__PURE__ */ jsx("span", {
600
+ "data-agent-count": true,
601
+ children: index.actors.filter((item) => item.kind !== "finding").length
602
+ })] }),
603
+ record.nodes.some((item) => item.kind === "session" && !item.agentId) && /* @__PURE__ */ jsxs("p", {
604
+ className: "small",
605
+ "data-graph-caption": true,
606
+ children: ["Not linked to an agent: ", record.nodes.filter((item) => item.kind === "session" && !item.agentId).map((item) => item.label).join(", ")]
607
+ }),
608
+ /* @__PURE__ */ jsx(AgentTree, {
609
+ index,
610
+ actor,
611
+ cutoff,
612
+ select: selectActor
613
+ })
614
+ ]
615
+ }), /* @__PURE__ */ jsxs("section", {
616
+ className: "conversation-panel",
617
+ "aria-label": "Agent record details",
618
+ children: [
619
+ /* @__PURE__ */ jsxs("div", {
620
+ className: "conversation-head",
621
+ children: [/* @__PURE__ */ jsxs("div", {
622
+ className: "agent-identity",
623
+ children: [/* @__PURE__ */ jsx("span", {
624
+ className: "agent-avatar",
625
+ "data-agent-avatar": true,
626
+ "aria-hidden": true,
627
+ children: roleOf(node).slice(0, 1).toUpperCase()
628
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
629
+ "data-agent-title": true,
630
+ children: node?.label ?? "Messages"
631
+ }), /* @__PURE__ */ jsx("p", {
632
+ "data-agent-role": true,
633
+ children: [
634
+ roleOf(node),
635
+ modelIdentity(node, record),
636
+ statusMeta(runStatus(node?.status ?? "unknown")).label
637
+ ].filter(Boolean).join(" · ")
638
+ })] })]
639
+ }), /* @__PURE__ */ jsx(Select$1, {
640
+ label: "Agent",
641
+ field: "agent",
642
+ value: actor,
643
+ onChange: selectActor,
644
+ children: index.actors.map((item) => /* @__PURE__ */ jsxs("option", {
645
+ value: item.id,
646
+ children: [
647
+ item.label,
648
+ " · ",
649
+ roleOf(item)
650
+ ]
651
+ }, item.id))
652
+ })]
653
+ }),
654
+ /* @__PURE__ */ jsx(Capture, {
655
+ record,
656
+ index,
657
+ actor
658
+ }),
659
+ /* @__PURE__ */ jsxs(Tabs, {
660
+ value: view,
661
+ onValueChange: (view) => update({ view }),
662
+ children: [
663
+ /* @__PURE__ */ jsx(TabsList, {
664
+ "aria-label": "Agent evidence",
665
+ children: [
666
+ "chat",
667
+ "source",
668
+ "usage"
669
+ ].map((tab) => /* @__PURE__ */ jsx(TabsTrigger, {
670
+ value: tab,
671
+ "data-view": tab,
672
+ children: tab === "chat" ? "Messages" : tab === "source" ? "Event details" : "Usage"
673
+ }, tab))
674
+ }),
675
+ /* @__PURE__ */ jsx(TabsContent, {
676
+ value: "chat",
677
+ forceMount: true,
678
+ hidden: view !== "chat",
679
+ "data-panel": "chat",
680
+ children: /* @__PURE__ */ jsx(Conversation, {
681
+ index,
682
+ actor,
683
+ cutoff,
684
+ query,
685
+ category,
686
+ selected: event?.id
687
+ })
688
+ }),
689
+ /* @__PURE__ */ jsx(TabsContent, {
690
+ value: "source",
691
+ forceMount: true,
692
+ hidden: view !== "source",
693
+ "data-panel": "source",
694
+ children: /* @__PURE__ */ jsx(EventDetails, {
695
+ event,
696
+ record,
697
+ index,
698
+ cutoff
699
+ })
700
+ }),
701
+ /* @__PURE__ */ jsxs(TabsContent, {
702
+ value: "usage",
703
+ forceMount: true,
704
+ hidden: view !== "usage",
705
+ "data-panel": "usage",
706
+ children: [/* @__PURE__ */ jsxs("div", {
707
+ className: "metric-controls",
708
+ children: [/* @__PURE__ */ jsxs(Select$1, {
709
+ label: "Measure",
710
+ field: "metric",
711
+ value: metric,
712
+ onChange: (value) => setMetric(value),
713
+ children: [
714
+ /* @__PURE__ */ jsx("option", {
715
+ value: "cumulative",
716
+ children: "Cumulative tokens"
717
+ }),
718
+ /* @__PURE__ */ jsx("option", {
719
+ value: "response",
720
+ children: "Tokens per response"
721
+ }),
722
+ /* @__PURE__ */ jsx("option", {
723
+ value: "tool-time",
724
+ children: "Tool return time"
725
+ })
726
+ ]
727
+ }), /* @__PURE__ */ jsxs(Select$1, {
728
+ label: "Horizontal axis",
729
+ field: "axis",
730
+ value: axis,
731
+ onChange: (value) => setAxis(value),
732
+ children: [/* @__PURE__ */ jsx("option", {
733
+ value: "time",
734
+ children: "Elapsed time"
735
+ }), /* @__PURE__ */ jsx("option", {
736
+ value: "order",
737
+ children: "Recorded order"
738
+ })]
739
+ })]
740
+ }), /* @__PURE__ */ jsx(RecordUsage, {
741
+ index,
742
+ cutoff,
743
+ actor,
744
+ metric,
745
+ axis,
746
+ inspect,
747
+ navigate
748
+ })]
749
+ })
750
+ ]
751
+ })
752
+ ]
753
+ })]
754
+ }),
755
+ /* @__PURE__ */ jsxs("div", {
756
+ className: "navigation",
757
+ children: [
758
+ /* @__PURE__ */ jsxs(Select$1, {
759
+ label: "Jump to a moment",
760
+ field: "moment",
761
+ value: event?.id ?? "",
762
+ onChange: (id) => {
763
+ const item = index.byEvent.get(id);
764
+ if (item) inspect(item);
765
+ },
766
+ children: [!visible.some((item) => item.id === event?.id) && /* @__PURE__ */ jsx("option", {
767
+ value: event?.id ?? "",
768
+ children: event ? "Selected event is outside this filter" : "Select an event"
769
+ }), visible.map((item) => /* @__PURE__ */ jsxs("option", {
770
+ value: item.id,
771
+ children: [
772
+ utcTime(item.at),
773
+ " · ",
774
+ item.label
775
+ ]
776
+ }, item.id))]
777
+ }),
778
+ /* @__PURE__ */ jsx("button", {
779
+ className: "ui-button",
780
+ "data-prev": true,
781
+ disabled: !visible.length,
782
+ onClick: () => navigate(-1),
783
+ children: "Previous"
784
+ }),
785
+ /* @__PURE__ */ jsx("button", {
786
+ className: "ui-button",
787
+ "data-next": true,
788
+ disabled: !visible.length,
789
+ onClick: () => navigate(1),
790
+ children: "Next"
791
+ }),
792
+ /* @__PURE__ */ jsxs("span", {
793
+ "data-position": true,
794
+ children: [
795
+ position >= 0 ? position + 1 : "—",
796
+ " / ",
797
+ visible.length
798
+ ]
799
+ }),
800
+ /* @__PURE__ */ jsx("button", {
801
+ className: "download-record",
802
+ "data-download": true,
803
+ onClick: () => download(JSON.stringify(record, null, 2), record.runId + ".json", "application/json"),
804
+ children: "Download record"
805
+ })
806
+ ]
807
+ }),
808
+ /* @__PURE__ */ jsxs("details", {
809
+ className: "assignment",
810
+ children: [
811
+ /* @__PURE__ */ jsx("summary", { children: "Original assignment" }),
812
+ /* @__PURE__ */ jsx("dl", {
813
+ "data-assignment": true,
814
+ children: Object.entries(record.assignment).filter(([key]) => key !== "source").map(([key, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: key.replace(/([a-z])([A-Z])/g, "$1 $2") }), /* @__PURE__ */ jsx("dd", { children: typeof value === "string" ? value : JSON.stringify(value, null, 2) })] }, key))
815
+ }),
816
+ record.assignment.source && /* @__PURE__ */ jsxs("details", {
817
+ className: "assignment-source",
818
+ children: [/* @__PURE__ */ jsx("summary", { children: "Provenance" }), /* @__PURE__ */ jsx("pre", { children: JSON.stringify(record.assignment.source, null, 2) })]
819
+ })
820
+ ]
821
+ }),
822
+ /* @__PURE__ */ jsxs("details", {
823
+ className: "coverage",
824
+ children: [/* @__PURE__ */ jsx("summary", { children: "Sources, redactions & capture gaps" }), /* @__PURE__ */ jsxs("div", {
825
+ "data-coverage": true,
826
+ children: [
827
+ record.coverage.publicContent && /* @__PURE__ */ jsx("p", { children: record.coverage.publicContent }),
828
+ record.coverage.categoryMethod && /* @__PURE__ */ jsx("p", { children: record.coverage.categoryMethod }),
829
+ record.coverage.cost && record.coverage.cost !== "Not recorded" && /* @__PURE__ */ jsxs("p", { children: ["Cost: ", record.coverage.cost] }),
830
+ record.terminal && /* @__PURE__ */ jsxs("p", { children: [
831
+ "Recorded terminal: ",
832
+ record.terminal.kind ?? "unknown",
833
+ record.terminal.reason ? ` · ${record.terminal.reason}` : ""
834
+ ] }),
835
+ /* @__PURE__ */ jsx("pre", { children: JSON.stringify({
836
+ coverage: record.coverage,
837
+ sources: record.sources
838
+ }, null, 2) })
839
+ ]
840
+ })]
841
+ })
842
+ ]
843
+ });
844
+ }
845
+ function Capture({ record, index, actor }) {
846
+ const node = index.nodes.get(actor), capture = node?.metadata && typeof node.metadata === "object" ? node.metadata : void 0;
847
+ const retained = index.byActor.get(actor)?.length ?? 0;
848
+ const note = typeof node?.captureNote === "string" ? node.captureNote : typeof capture?.captureReason === "string" ? capture.captureReason : node?.capture && node.capture.status !== "complete" ? (record.coverage?.gaps ?? []).filter((gap) => gap.nodeId === actor).map((gap) => gap.detail).join(" ") : null;
849
+ const label = capture?.captureStatus === "retained-partial" || node?.capture?.status === "lossy" ? "Partial capture" : capture?.captureStatus === "missing-source" || node?.capture?.status === "absent" || !retained ? "Conversation unavailable" : capture?.captureStatus === "retained" ? "Retained capture" : null;
850
+ return label || note || node?.assignment ? /* @__PURE__ */ jsxs("div", {
851
+ className: "agent-capture-summary",
852
+ role: "status",
853
+ children: [
854
+ label,
855
+ note && /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", { children: "Capture details" }), /* @__PURE__ */ jsx("p", { children: note })] }),
856
+ node?.assignment && /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", { children: "What this agent did" }), /* @__PURE__ */ jsx("p", { children: node.assignment })] })
857
+ ]
858
+ }) : null;
859
+ }
860
+ function EventDetails({ event, record, index, cutoff }) {
861
+ if (!event) return /* @__PURE__ */ jsx("p", {
862
+ className: "chat-empty",
863
+ children: "No source event selected."
864
+ });
865
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
866
+ /* @__PURE__ */ jsx("h4", {
867
+ "data-event-title": true,
868
+ children: event.label
869
+ }),
870
+ /* @__PURE__ */ jsxs("p", {
871
+ className: "small",
872
+ "data-event-meta": true,
873
+ children: [
874
+ utcTime(event.at, true),
875
+ " · ",
876
+ index.nodes.get(index.canonical(event.node))?.label ?? event.node
877
+ ]
878
+ }),
879
+ ms(event.at) > cutoff ? /* @__PURE__ */ jsx("p", { children: "This event occurs after the selected time. Advance the timeline to read it." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
880
+ textOf(event) && /* @__PURE__ */ jsx(StructuredContent, { text: textOf(event) }),
881
+ event.detail.publicToolCalls?.map((call) => /* @__PURE__ */ jsxs("section", {
882
+ className: "event-tool",
883
+ children: [/* @__PURE__ */ jsx("h4", { children: call.name }), call.input === void 0 ? /* @__PURE__ */ jsx("p", {
884
+ className: "publication-note",
885
+ children: "Args not captured."
886
+ }) : /* @__PURE__ */ jsx(StructuredContent, {
887
+ text: call.input,
888
+ rawLabel: "Raw input"
889
+ })]
890
+ }, call.id)),
891
+ !textOf(event) && !event.detail.publicToolCalls?.length && /* @__PURE__ */ jsx("p", { children: event.detail.contentOmitted ?? event.detail.publicationNote ?? "No visible message or tool payload was retained for this event." }),
892
+ /* @__PURE__ */ jsxs("details", {
893
+ className: "raw-data",
894
+ children: [
895
+ /* @__PURE__ */ jsx("summary", { children: "Raw event and source" }),
896
+ /* @__PURE__ */ jsx("pre", {
897
+ "data-event-detail": true,
898
+ children: JSON.stringify(event.detail, null, 2)
899
+ }),
900
+ /* @__PURE__ */ jsx("pre", {
901
+ "data-event-source": true,
902
+ children: JSON.stringify(eventSource(event, record, index), null, 2)
903
+ })
904
+ ]
905
+ })
906
+ ] })
907
+ ] });
908
+ }
909
+ // src/record/source-search.ts
910
+ /** Render only source IDs and bytes already supplied in the selected play. */
911
+ function readSourceSearch(value, playId, documents, revision) {
912
+ if (!value || typeof value !== "object") throw new Error("Source response unavailable");
913
+ const result = value;
914
+ if (result.scopeId !== playId || typeof result.revision !== "string" || revision !== void 0 && result.revision !== revision || !Array.isArray(result.hits)) throw new Error("Source response does not match this play");
915
+ const excerpts = /* @__PURE__ */ new Map();
916
+ return {
917
+ documents: result.hits.map((hit) => {
918
+ if (!hit || typeof hit !== "object") throw new Error("Invalid source response");
919
+ const source = hit.source;
920
+ if (!source || typeof source !== "object") throw new Error("Invalid source response");
921
+ const item = source;
922
+ const document = documents.find((doc) => doc.id === item.id);
923
+ if (!document || document.sha256 !== item.contentHash || document.content !== item.text) throw new Error("Source bytes do not match this retained play");
924
+ excerpts.set(document.id, document.content);
925
+ return document;
926
+ }),
927
+ indexedAt: typeof result.indexedAt === "string" ? result.indexedAt : null,
928
+ revision: result.revision,
929
+ excerpts
930
+ };
931
+ }
932
+ // src/record/KnowledgeBrowser.tsx
933
+ /** Only exact retained paths resolve. This never reads the host filesystem. */
934
+ function resolveDocument(documents, href, from) {
935
+ if (/^[a-z][a-z\d+.-]*:/i.test(href) || href.startsWith("//")) return void 0;
936
+ let path = href.split("#")[0] ?? "";
937
+ try {
938
+ path = decodeURIComponent(path);
939
+ } catch {
940
+ return;
941
+ }
942
+ if (!path) return documents.find((item) => item.path === from);
943
+ const alias = (item, value) => item.sourcePath === value || Array.isArray(item.aliases) && item.aliases.includes(value);
944
+ const unique = (matches) => matches.length === 1 ? matches[0] : void 0;
945
+ if (!from || path.startsWith("/")) {
946
+ const exact = unique(documents.filter((item) => item.path === path || alias(item, path)));
947
+ if (exact) return exact;
948
+ }
949
+ const segments = (path.startsWith("/") ? path : (from?.split("/").slice(0, -1).join("/") ?? "") + "/" + path).split("/");
950
+ const normalized = [];
951
+ for (const segment of segments) if (segment === "..") normalized.pop();
952
+ else if (segment && segment !== ".") normalized.push(segment);
953
+ return unique(documents.filter((item) => item.path.replace(/^\//, "") === normalized.join("/"))) ?? unique(documents.filter((item) => alias(item, path)));
954
+ }
955
+ function snippet(content, words) {
956
+ const lower = content.toLowerCase();
957
+ const positions = words.map((word) => lower.indexOf(word)).filter((index) => index >= 0);
958
+ const start = positions.length ? Math.max(0, Math.min(...positions) - 45) : 0;
959
+ return (start ? "…" : "") + content.slice(start, start + 160).replace(/\s+/g, " ") + (content.length > start + 160 ? "…" : "");
960
+ }
961
+ function KnowledgeBrowser({ documents, selection, onSelect, onClear, reportsOnly = false, playId, sourceSearchEndpoint }) {
962
+ const [query, setQuery] = useState("");
963
+ const [kind, setKind] = useState("all");
964
+ const [submittedQuery, setSubmittedQuery] = useState("");
965
+ const [result, setResult] = useState();
966
+ const [busy, setBusy] = useState(false);
967
+ const [error, setError] = useState("");
968
+ const request = useRef(void 0);
969
+ useEffect(() => {
970
+ request.current?.abort();
971
+ setResult(void 0);
972
+ setError("");
973
+ setBusy(false);
974
+ return () => request.current?.abort();
975
+ }, [
976
+ documents,
977
+ playId,
978
+ sourceSearchEndpoint
979
+ ]);
980
+ const index = useMemo(() => documents.map((document) => ({
981
+ document,
982
+ text: [
983
+ document.title,
984
+ document.path,
985
+ document.content
986
+ ].join("\n").toLowerCase()
987
+ })), [documents]);
988
+ const words = (result ? submittedQuery : query).toLowerCase().trim().split(/\s+/).filter(Boolean);
989
+ const selected = documents.find((document) => document.path === selection?.path);
990
+ const visible = (result ? result.documents.map((document) => ({
991
+ document,
992
+ text: document.content.toLowerCase()
993
+ })) : index).filter(({ document, text }) => (!reportsOnly || document.kind === "report") && (kind === "all" || document.kind === kind) && (sourceSearchEndpoint || words.every((word) => text.includes(word))));
994
+ async function search() {
995
+ if (!sourceSearchEndpoint || !playId || !query.trim()) return;
996
+ request.current?.abort();
997
+ const controller = new AbortController();
998
+ request.current = controller;
999
+ setBusy(true);
1000
+ setError("");
1001
+ setResult(void 0);
1002
+ setSubmittedQuery(query.trim());
1003
+ try {
1004
+ const params = new URLSearchParams({
1005
+ play: playId,
1006
+ q: query.trim(),
1007
+ limit: "10"
1008
+ });
1009
+ const response = await fetch(sourceSearchEndpoint + "?" + params, {
1010
+ signal: controller.signal,
1011
+ credentials: "same-origin"
1012
+ });
1013
+ if (!response.ok) throw new Error("Source index unavailable");
1014
+ const next = readSourceSearch(await response.json(), playId, documents, sourceSearchEndpoint.match(/\/revisions\/([^/]+)\//)?.[1]);
1015
+ if (!controller.signal.aborted) setResult(next);
1016
+ } catch {
1017
+ if (!controller.signal.aborted) setError("Source search failed or returned text that does not match these sources.");
1018
+ } finally {
1019
+ if (!controller.signal.aborted) setBusy(false);
1020
+ }
1021
+ }
1022
+ if (selected) return /* @__PURE__ */ jsxs("section", {
1023
+ className: "research-source-reader",
1024
+ "aria-label": "Selected source",
1025
+ children: [/* @__PURE__ */ jsx("button", {
1026
+ className: "ui-button source-back",
1027
+ onClick: onClear,
1028
+ children: "← All sources"
1029
+ }), /* @__PURE__ */ jsx(DocumentReader, {
1030
+ document: selected,
1031
+ documents,
1032
+ selection,
1033
+ onSelect
1034
+ }, selected.id)]
1035
+ });
1036
+ return /* @__PURE__ */ jsxs("section", {
1037
+ className: "rr-knowledge flat-sources",
1038
+ "aria-label": "Sources",
1039
+ children: [
1040
+ /* @__PURE__ */ jsxs("form", {
1041
+ className: "rr-knowledge-controls",
1042
+ onSubmit: (event) => {
1043
+ event.preventDefault();
1044
+ search();
1045
+ },
1046
+ children: [
1047
+ /* @__PURE__ */ jsxs("label", {
1048
+ className: "ui-field",
1049
+ children: [/* @__PURE__ */ jsx("span", {
1050
+ className: "ui-label",
1051
+ children: sourceSearchEndpoint ? "Search this play’s sources" : "Search retained sources"
1052
+ }), /* @__PURE__ */ jsx("input", {
1053
+ type: "search",
1054
+ value: query,
1055
+ maxLength: 500,
1056
+ placeholder: "Question keywords, result, or source…",
1057
+ onChange: (event) => setQuery(event.target.value)
1058
+ })]
1059
+ }),
1060
+ sourceSearchEndpoint && /* @__PURE__ */ jsx("button", {
1061
+ className: "ui-button",
1062
+ disabled: busy || !query.trim(),
1063
+ children: busy ? "Searching…" : "Search"
1064
+ }),
1065
+ /* @__PURE__ */ jsxs("label", {
1066
+ className: "ui-field",
1067
+ children: [/* @__PURE__ */ jsx("span", {
1068
+ className: "ui-label",
1069
+ children: "File kind"
1070
+ }), /* @__PURE__ */ jsxs("select", {
1071
+ value: kind,
1072
+ onChange: (event) => setKind(event.target.value),
1073
+ children: [
1074
+ /* @__PURE__ */ jsx("option", {
1075
+ value: "all",
1076
+ children: "All files"
1077
+ }),
1078
+ /* @__PURE__ */ jsx("option", {
1079
+ value: "report",
1080
+ children: "Reports"
1081
+ }),
1082
+ /* @__PURE__ */ jsx("option", {
1083
+ value: "knowledge",
1084
+ children: "Research notes"
1085
+ }),
1086
+ /* @__PURE__ */ jsx("option", {
1087
+ value: "code",
1088
+ children: "Code"
1089
+ }),
1090
+ /* @__PURE__ */ jsx("option", {
1091
+ value: "data",
1092
+ children: "Data"
1093
+ })
1094
+ ]
1095
+ })]
1096
+ }),
1097
+ result && /* @__PURE__ */ jsx("button", {
1098
+ type: "button",
1099
+ className: "ui-button",
1100
+ onClick: () => {
1101
+ setResult(void 0);
1102
+ setQuery("");
1103
+ setKind("all");
1104
+ },
1105
+ children: "Browse all"
1106
+ })
1107
+ ]
1108
+ }),
1109
+ (busy || result) && /* @__PURE__ */ jsx("p", {
1110
+ className: "small",
1111
+ role: "status",
1112
+ children: busy ? "Searching…" : result?.indexedAt ? `Indexed ${result.indexedAt}` : "Index time unknown"
1113
+ }),
1114
+ error && /* @__PURE__ */ jsx("p", {
1115
+ role: "alert",
1116
+ className: "chat-empty",
1117
+ children: error
1118
+ }),
1119
+ !busy && !error && /* @__PURE__ */ jsx("nav", {
1120
+ className: "source-results",
1121
+ "aria-label": "Retained documents",
1122
+ children: visible.map(({ document }) => /* @__PURE__ */ jsxs("button", {
1123
+ type: "button",
1124
+ onClick: () => {
1125
+ const found = words.map((word) => document.content.toLowerCase().indexOf(word)).filter((index) => index >= 0);
1126
+ onSelect({
1127
+ path: document.path,
1128
+ ...result && found.length ? { line: document.content.slice(0, Math.min(...found)).split("\n").length } : {}
1129
+ });
1130
+ },
1131
+ children: [
1132
+ /* @__PURE__ */ jsx("span", {
1133
+ className: "source-kind",
1134
+ children: document.kind
1135
+ }),
1136
+ /* @__PURE__ */ jsx("strong", { children: document.title }),
1137
+ /* @__PURE__ */ jsx("small", { children: document.path }),
1138
+ words.length > 0 && /* @__PURE__ */ jsx("span", {
1139
+ className: "rr-search-excerpt",
1140
+ children: snippet(document.content, words)
1141
+ })
1142
+ ]
1143
+ }, document.id))
1144
+ }),
1145
+ !busy && !error && !visible.length && /* @__PURE__ */ jsx("p", {
1146
+ className: "chat-empty",
1147
+ children: "No matching sources. Only retained files are searched."
1148
+ })
1149
+ ]
1150
+ });
1151
+ }
1152
+ function DocumentReader({ document, documents, selection, onSelect }) {
1153
+ const [source, setSource] = useState(document.kind === "code" || document.kind === "data");
1154
+ const reader = useRef(null);
1155
+ const download = useDownload();
1156
+ const requestedLine = selection?.line;
1157
+ const anchor = selection?.anchor;
1158
+ useEffect(() => {
1159
+ if (requestedLine && source) reader.current?.querySelector(`[data-line="${requestedLine}"]`)?.scrollIntoView({ block: "center" });
1160
+ if (anchor && !source) {
1161
+ let decoded = anchor;
1162
+ try {
1163
+ decoded = decodeURIComponent(anchor);
1164
+ } catch {}
1165
+ Array.from(reader.current?.querySelectorAll("h1,h2,h3,h4,h5,h6,[id]") ?? []).find((node) => node.id === decoded || node.textContent?.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-") === decoded)?.scrollIntoView({ block: "start" });
1166
+ }
1167
+ }, [selection, source]);
1168
+ return /* @__PURE__ */ jsxs("article", {
1169
+ className: "rr-document",
1170
+ children: [
1171
+ /* @__PURE__ */ jsxs("header", {
1172
+ className: "rr-document-header",
1173
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", { children: document.title }), /* @__PURE__ */ jsx("p", {
1174
+ className: "rr-document-path",
1175
+ children: document.path
1176
+ })] }), /* @__PURE__ */ jsxs("div", {
1177
+ className: "rr-document-actions",
1178
+ children: [/* @__PURE__ */ jsx("button", {
1179
+ className: "ui-button",
1180
+ type: "button",
1181
+ "aria-pressed": source,
1182
+ onClick: () => setSource(!source),
1183
+ children: source ? "Read document" : requestedLine ? "View cited line" : "View source"
1184
+ }), /* @__PURE__ */ jsx("button", {
1185
+ className: "ui-button",
1186
+ type: "button",
1187
+ onClick: () => download(document.content, document.path.split("/").pop() || "document.txt", "text/plain;charset=utf-8"),
1188
+ children: "Download original"
1189
+ })]
1190
+ })]
1191
+ }),
1192
+ document.sha256 && /* @__PURE__ */ jsxs("details", {
1193
+ className: "rr-document-provenance",
1194
+ children: [
1195
+ /* @__PURE__ */ jsx("summary", { children: "Source fingerprint" }),
1196
+ /* @__PURE__ */ jsxs("code", { children: ["SHA-256 ", document.sha256] }),
1197
+ /* @__PURE__ */ jsx("p", { children: "Supplied with the report; not checked against the archive." })
1198
+ ]
1199
+ }),
1200
+ requestedLine && /* @__PURE__ */ jsx("p", {
1201
+ className: "rr-meta",
1202
+ children: requestedLine > document.content.split("\n").length ? `Referenced line ${requestedLine} is outside this document.` : `Cited line ${requestedLine}${source ? " · source view" : ""}`
1203
+ }),
1204
+ /* @__PURE__ */ jsx("div", {
1205
+ className: "rr-document-content",
1206
+ ref: reader,
1207
+ children: source ? /* @__PURE__ */ jsx("pre", {
1208
+ className: "rr-document-source",
1209
+ children: /* @__PURE__ */ jsx("code", { children: document.content.split("\n").map((line, i) => /* @__PURE__ */ jsxs("span", {
1210
+ "data-line": i + 1,
1211
+ "data-highlight": i + 1 === requestedLine || void 0,
1212
+ children: [/* @__PURE__ */ jsx("span", {
1213
+ "aria-hidden": "true",
1214
+ children: i + 1
1215
+ }), line || "\n"]
1216
+ }, i)) })
1217
+ }) : /* @__PURE__ */ jsx(ResearchMarkdown, {
1218
+ document,
1219
+ documents,
1220
+ onSelect
1221
+ })
1222
+ })
1223
+ ]
1224
+ });
1225
+ }
1226
+ const ResearchMarkdown = memo(function ResearchMarkdown({ document, documents, onSelect }) {
1227
+ return /* @__PURE__ */ jsx("div", {
1228
+ className: "rr-markdown",
1229
+ children: /* @__PURE__ */ jsx(Markdown, {
1230
+ skipHtml: true,
1231
+ remarkPlugins: [remarkGfm, [remarkMath, { singleDollarTextMath: false }]],
1232
+ rehypePlugins: [[rehypeKatex, {
1233
+ output: "mathml",
1234
+ trust: false,
1235
+ strict: "warn",
1236
+ maxExpand: 1e3,
1237
+ maxSize: 20
1238
+ }]],
1239
+ urlTransform: (url) => url,
1240
+ components: {
1241
+ a: ({ href = "", children }) => {
1242
+ const target = resolveDocument(documents, href, document.path);
1243
+ if (target) return /* @__PURE__ */ jsx("button", {
1244
+ type: "button",
1245
+ className: "rr-inline-link",
1246
+ onClick: () => onSelect({
1247
+ path: target.path,
1248
+ anchor: href.split("#")[1]
1249
+ }),
1250
+ children
1251
+ });
1252
+ if (/^https?:\/\/[^\s]+$/i.test(href)) return /* @__PURE__ */ jsx("a", {
1253
+ href,
1254
+ target: "_blank",
1255
+ rel: "noopener noreferrer",
1256
+ children
1257
+ });
1258
+ return /* @__PURE__ */ jsx("span", {
1259
+ title: href ? `Source not included: ${href}` : void 0,
1260
+ children
1261
+ });
1262
+ },
1263
+ img: ({ alt, src }) => /* @__PURE__ */ jsxs("span", {
1264
+ className: "rr-media-reference",
1265
+ children: [alt || "Image", src ? ` (${src})` : ""]
1266
+ }),
1267
+ table: ({ children }) => /* @__PURE__ */ jsx("div", {
1268
+ className: "rr-table-scroll",
1269
+ children: /* @__PURE__ */ jsx("table", { children })
1270
+ })
1271
+ },
1272
+ children: document.content
1273
+ })
1274
+ });
1275
+ });
1276
+ // src/record/OutcomeBar.tsx
1277
+ const WORD = {
1278
+ ok: "ok",
1279
+ quota: "quota",
1280
+ lost: "lost",
1281
+ "rate-limit": "rate limit",
1282
+ oom: "OOM",
1283
+ refused: "refused",
1284
+ aborted: "aborted",
1285
+ other: "other"
1286
+ };
1287
+ const STATE = {
1288
+ ok: "ok",
1289
+ quota: "failed",
1290
+ lost: "failed",
1291
+ "rate-limit": "failed",
1292
+ oom: "failed",
1293
+ refused: "failed",
1294
+ aborted: "other",
1295
+ other: "other"
1296
+ };
1297
+ const COLOR = {
1298
+ ok: "var(--ar-c-ok)",
1299
+ failed: "var(--ar-c-fail)",
1300
+ other: "var(--ar-c-neutral)"
1301
+ };
1302
+ function OutcomeLegend({ outcomes }) {
1303
+ return /* @__PURE__ */ jsx("ul", {
1304
+ className: "outcome-legend",
1305
+ "aria-label": "Attempt outcomes",
1306
+ children: [
1307
+ "ok",
1308
+ "failed",
1309
+ "other"
1310
+ ].filter((state) => outcomes.some((outcome) => STATE[outcome] === state)).map((state) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("i", { style: { background: COLOR[state] } }), state] }, state))
1311
+ });
1312
+ }
1313
+ /** Recorded agent-hours share the report's axis; absent hours remain unknown. */
1314
+ function OutcomeBar({ row, max }) {
1315
+ const by = row.hoursByOutcome, total = Object.values(by ?? {}).reduce((sum, value) => sum + value, 0);
1316
+ if (!total) return /* @__PURE__ */ jsx("span", {
1317
+ className: "faint",
1318
+ title: "attempts not recorded",
1319
+ children: "—"
1320
+ });
1321
+ const parts = OUTCOME_ORDER.filter((outcome) => (by?.[outcome] ?? 0) > 0);
1322
+ const label = (outcome) => `${WORD[outcome]} ${by[outcome].toFixed(2)} h${row.triesByOutcome?.[outcome] !== void 0 ? ` · ${row.triesByOutcome[outcome]} tries` : ""}`;
1323
+ return /* @__PURE__ */ jsxs("span", {
1324
+ className: "outcome-bar",
1325
+ title: parts.map(label).join("\n"),
1326
+ children: [/* @__PURE__ */ jsx("span", {
1327
+ className: "outcome-track",
1328
+ style: { width: `${Math.max(2, total / max * 100)}%` },
1329
+ children: parts.map((outcome) => /* @__PURE__ */ jsx("i", {
1330
+ "data-outcome": outcome,
1331
+ title: label(outcome),
1332
+ style: {
1333
+ flexGrow: by[outcome],
1334
+ background: COLOR[STATE[outcome] ?? "other"]
1335
+ }
1336
+ }, outcome))
1337
+ }), /* @__PURE__ */ jsxs("span", {
1338
+ className: "outcome-total num",
1339
+ children: [total.toFixed(1), " h"]
1340
+ })]
1341
+ });
1342
+ }
1343
+ // src/record/ResearchReport.tsx
1344
+ const DocumentContext = createContext({
1345
+ documents: [],
1346
+ open: () => {}
1347
+ });
1348
+ function ResearchReport({ report, theme = "auto", defaultPlayId, onPlayChange, defaultView, onViewChange, defaultActivitySelection, onActivityChange, defaultDocumentSelection, onDocumentChange, sourceSearchEndpoint, figures, className = "" }) {
1349
+ sourceSearchEndpoint = parseReportOptions({ sourceSearchEndpoint }).sourceSearchEndpoint;
1350
+ const [selected, setSelected] = useState(defaultDocumentSelection?.playId ?? defaultPlayId ?? report.plays[0]?.id);
1351
+ const [query, setQuery] = useState("");
1352
+ const download = useDownload();
1353
+ const plays = report.plays.filter((play) => [play.title, play.id].join(" ").toLowerCase().includes(query.trim().toLowerCase()));
1354
+ const play = report.plays.find((item) => item.id === selected) ?? report.plays[0];
1355
+ function choose(id) {
1356
+ setSelected(id);
1357
+ onPlayChange?.(id);
1358
+ }
1359
+ return /* @__PURE__ */ jsxs("article", {
1360
+ className: "agent-record research-report research-workspace " + className,
1361
+ "data-theme": theme,
1362
+ children: [
1363
+ /* @__PURE__ */ jsxs("aside", {
1364
+ className: "research-rail",
1365
+ children: [
1366
+ /* @__PURE__ */ jsx("h1", { children: report.title }),
1367
+ /* @__PURE__ */ jsxs("label", {
1368
+ className: "ui-field",
1369
+ children: [/* @__PURE__ */ jsx("span", {
1370
+ className: "ui-label",
1371
+ children: "Find a play"
1372
+ }), /* @__PURE__ */ jsx("input", {
1373
+ type: "search",
1374
+ value: query,
1375
+ onChange: (event) => setQuery(event.target.value),
1376
+ placeholder: "Name…"
1377
+ })]
1378
+ }),
1379
+ /* @__PURE__ */ jsxs("nav", {
1380
+ className: "research-play-list",
1381
+ "aria-label": "Research plays",
1382
+ children: [plays.map((item) => /* @__PURE__ */ jsxs("button", {
1383
+ "aria-current": item.id === play?.id ? "page" : void 0,
1384
+ onClick: () => choose(item.id),
1385
+ children: [/* @__PURE__ */ jsx("span", { children: item.title }), /* @__PURE__ */ jsxs("small", { children: [
1386
+ item.claims.length,
1387
+ " results · ",
1388
+ item.documents.length,
1389
+ " sources"
1390
+ ] })]
1391
+ }, item.id)), !plays.length && /* @__PURE__ */ jsx("p", {
1392
+ className: "small",
1393
+ children: "No matching play."
1394
+ })]
1395
+ }),
1396
+ /* @__PURE__ */ jsxs("details", {
1397
+ className: "research-info",
1398
+ children: [
1399
+ /* @__PURE__ */ jsx("summary", { children: "Report summary and exports" }),
1400
+ /* @__PURE__ */ jsx("p", {
1401
+ className: "small",
1402
+ children: report.summary
1403
+ }),
1404
+ /* @__PURE__ */ jsxs("p", {
1405
+ className: "small",
1406
+ children: ["Authored ", utcTime(report.generatedAt, true)]
1407
+ }),
1408
+ report.freshness && /* @__PURE__ */ jsx("p", {
1409
+ className: "small",
1410
+ children: report.freshness.message
1411
+ }),
1412
+ /* @__PURE__ */ jsxs("p", {
1413
+ className: "small",
1414
+ children: [
1415
+ "Assessed by ",
1416
+ report.assessmentBy ?? "the report author",
1417
+ "; assessment labels are judgments, not independent verification."
1418
+ ]
1419
+ }),
1420
+ report.limitations.length > 0 && /* @__PURE__ */ jsx("ul", { children: report.limitations.map((item, i) => /* @__PURE__ */ jsx("li", { children: item }, i)) }),
1421
+ report.questionCoverage.length > 0 && /* @__PURE__ */ jsx(Questions, {
1422
+ questions: report.questionCoverage,
1423
+ inspect: () => {},
1424
+ printOnly: false
1425
+ }),
1426
+ /* @__PURE__ */ jsxs("div", {
1427
+ className: "research-exports",
1428
+ children: [
1429
+ /* @__PURE__ */ jsx("button", {
1430
+ className: "ui-button",
1431
+ onClick: () => download(reportToLatex(report), report.id + ".tex", "application/x-tex"),
1432
+ children: "Export LaTeX"
1433
+ }),
1434
+ /* @__PURE__ */ jsx("button", {
1435
+ className: "ui-button",
1436
+ onClick: () => download(JSON.stringify(report, null, 2), report.id + ".json", "application/json"),
1437
+ children: "Download data"
1438
+ }),
1439
+ /* @__PURE__ */ jsx("button", {
1440
+ className: "ui-button",
1441
+ onClick: () => window.print(),
1442
+ children: "Print results"
1443
+ })
1444
+ ]
1445
+ })
1446
+ ]
1447
+ })
1448
+ ]
1449
+ }),
1450
+ play ? /* @__PURE__ */ jsx(PlayReport, {
1451
+ play,
1452
+ report,
1453
+ theme,
1454
+ initialView: defaultView,
1455
+ initialActivity: defaultActivitySelection,
1456
+ onActivityChange,
1457
+ initialDocument: defaultDocumentSelection?.playId === play.id ? defaultDocumentSelection : void 0,
1458
+ onDocumentChange,
1459
+ onViewChange,
1460
+ sourceSearchEndpoint,
1461
+ figures
1462
+ }, play.id) : /* @__PURE__ */ jsx("p", {
1463
+ className: "chat-empty",
1464
+ children: "No plays were included."
1465
+ }),
1466
+ /* @__PURE__ */ jsx("div", {
1467
+ className: "rr-print",
1468
+ children: report.plays.map((item) => /* @__PURE__ */ jsx(PlayReport, {
1469
+ play: item,
1470
+ report,
1471
+ theme,
1472
+ printOnly: true
1473
+ }, item.id))
1474
+ })
1475
+ ]
1476
+ });
1477
+ }
1478
+ function PlayReport({ play, report, theme, printOnly = false, initialDocument, initialView, initialActivity, onActivityChange, onDocumentChange, onViewChange, sourceSearchEndpoint, figures }) {
1479
+ const [view, setView] = useState(initialDocument ? "sources" : initialView ?? "results");
1480
+ const [selection, setSelection] = useState(initialActivity);
1481
+ const [documentSelection, setDocumentSelection] = useState(initialDocument);
1482
+ const uid = useId();
1483
+ const download = useDownload();
1484
+ const anchor = (id) => printOnly ? void 0 : id;
1485
+ function show(next) {
1486
+ if (next === "sources") setDocumentSelection(void 0);
1487
+ setView(next);
1488
+ onViewChange?.(next);
1489
+ }
1490
+ function inspect(eventId) {
1491
+ if (!play.record?.events.find((item) => item.id === eventId) || !play.record) return;
1492
+ setSelection({
1493
+ runId: play.record.runId,
1494
+ eventId,
1495
+ view: "source"
1496
+ });
1497
+ show("activity");
1498
+ onActivityChange?.(play.id, {
1499
+ runId: play.record.runId,
1500
+ eventId,
1501
+ view: "source"
1502
+ });
1503
+ }
1504
+ function openDocument(next) {
1505
+ setDocumentSelection(next);
1506
+ setView("sources");
1507
+ onDocumentChange?.({
1508
+ playId: play.id,
1509
+ ...next
1510
+ });
1511
+ }
1512
+ return /* @__PURE__ */ jsx(DocumentContext.Provider, {
1513
+ value: {
1514
+ documents: play.documents,
1515
+ open: openDocument
1516
+ },
1517
+ children: /* @__PURE__ */ jsxs("section", {
1518
+ className: "research-main",
1519
+ "aria-labelledby": uid,
1520
+ children: [
1521
+ /* @__PURE__ */ jsxs("header", {
1522
+ className: "research-heading",
1523
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
1524
+ id: uid,
1525
+ children: play.title
1526
+ }), /* @__PURE__ */ jsxs("p", {
1527
+ className: "small",
1528
+ children: [
1529
+ play.updatedAt ? `Updated ${utcTime(play.updatedAt, true)}` : "Update time unknown",
1530
+ " · ",
1531
+ play.status ?? "State unknown",
1532
+ play.href && /* @__PURE__ */ jsxs(Fragment, { children: [" · ", /* @__PURE__ */ jsx("a", {
1533
+ href: play.href,
1534
+ children: "Play page"
1535
+ })] })
1536
+ ]
1537
+ })] }), !printOnly && /* @__PURE__ */ jsx("button", {
1538
+ className: "ui-button",
1539
+ onClick: () => download(reportToLatex(report, play.id), play.id + ".tex", "application/x-tex"),
1540
+ children: "Export"
1541
+ })]
1542
+ }),
1543
+ !printOnly && /* @__PURE__ */ jsx("nav", {
1544
+ className: "research-sections",
1545
+ "aria-label": "Play sections",
1546
+ children: [
1547
+ "results",
1548
+ "activity",
1549
+ "sources"
1550
+ ].map((section) => /* @__PURE__ */ jsxs("button", {
1551
+ "aria-current": view === section ? "page" : void 0,
1552
+ onClick: () => show(section),
1553
+ children: [section[0].toUpperCase() + section.slice(1), section === "sources" ? ` · ${play.documents.length}` : ""]
1554
+ }, section))
1555
+ }),
1556
+ (printOnly || view === "results") && /* @__PURE__ */ jsxs("div", {
1557
+ className: "research-results",
1558
+ children: [
1559
+ play.verdict ? /* @__PURE__ */ jsxs("section", {
1560
+ className: "rr-section rr-lead-verdict",
1561
+ id: anchor("verdict"),
1562
+ "data-section": "verdict",
1563
+ "aria-label": "Verdict",
1564
+ children: [
1565
+ /* @__PURE__ */ jsx("h3", { children: "Verdict" }),
1566
+ play.verdict.status && /* @__PURE__ */ jsx("p", {
1567
+ className: "rr-assessment",
1568
+ "data-assessment": play.verdict.status,
1569
+ children: play.verdict.status.replace(/-/g, " ")
1570
+ }),
1571
+ /* @__PURE__ */ jsx("p", {
1572
+ className: "research-summary",
1573
+ children: play.verdict.sentence
1574
+ }),
1575
+ /* @__PURE__ */ jsx(Sources, {
1576
+ sources: play.verdict.evidence,
1577
+ inspect,
1578
+ printOnly,
1579
+ inline: true
1580
+ })
1581
+ ]
1582
+ }) : /* @__PURE__ */ jsx("p", {
1583
+ className: "research-summary",
1584
+ children: play.summary
1585
+ }),
1586
+ /* @__PURE__ */ jsx("div", {
1587
+ className: "research-result-list",
1588
+ children: play.claims.map((claim) => /* @__PURE__ */ jsxs("article", {
1589
+ className: "research-result",
1590
+ children: [
1591
+ /* @__PURE__ */ jsx("p", {
1592
+ className: "rr-assessment",
1593
+ "data-assessment": claim.status,
1594
+ children: claim.status[0].toUpperCase() + claim.status.slice(1)
1595
+ }),
1596
+ /* @__PURE__ */ jsx("h3", { children: typeof claim.title === "string" ? claim.title : claim.statement }),
1597
+ typeof claim.title === "string" && /* @__PURE__ */ jsx("p", { children: claim.statement }),
1598
+ claim.method && /* @__PURE__ */ jsx("p", {
1599
+ className: "small",
1600
+ children: claim.method
1601
+ }),
1602
+ claim.limitations.length > 0 && /* @__PURE__ */ jsx("ul", {
1603
+ className: "rr-limitations",
1604
+ children: claim.limitations.map((item, i) => /* @__PURE__ */ jsx("li", { children: item }, i))
1605
+ }),
1606
+ claim.evidence.length > 0 ? /* @__PURE__ */ jsx(Sources, {
1607
+ sources: claim.evidence,
1608
+ inspect,
1609
+ printOnly
1610
+ }) : /* @__PURE__ */ jsx("p", {
1611
+ className: "small",
1612
+ children: "Supporting sources not supplied."
1613
+ })
1614
+ ]
1615
+ }, claim.id))
1616
+ }),
1617
+ !play.claims.length && !play.questionCoverage.length && /* @__PURE__ */ jsx("p", {
1618
+ className: "chat-empty",
1619
+ children: "No assessed results supplied."
1620
+ }),
1621
+ play.questionCoverage.length > 0 && /* @__PURE__ */ jsx(Questions, {
1622
+ questions: play.questionCoverage,
1623
+ inspect,
1624
+ printOnly,
1625
+ open: !play.claims.length,
1626
+ id: anchor("questions")
1627
+ }),
1628
+ play.sections?.map((section) => /* @__PURE__ */ jsxs("section", {
1629
+ className: "rr-section",
1630
+ id: anchor(section.id),
1631
+ "data-section": section.id,
1632
+ "aria-label": section.title,
1633
+ children: [
1634
+ /* @__PURE__ */ jsx("h3", { children: section.title }),
1635
+ section.summary && /* @__PURE__ */ jsx("p", { children: section.summary }),
1636
+ !printOnly && figures?.[section.id],
1637
+ play.metrics.some((metric) => metric.section === section.id) && /* @__PURE__ */ jsx("dl", {
1638
+ className: "rr-metrics rr-fact-grid",
1639
+ children: play.metrics.filter((metric) => metric.section === section.id).map((metric) => /* @__PURE__ */ jsx(Fact, {
1640
+ metric,
1641
+ inspect,
1642
+ printOnly
1643
+ }, metric.id))
1644
+ }),
1645
+ play.tables?.filter((table) => table.section === section.id).map((table) => /* @__PURE__ */ jsx(Table, {
1646
+ table,
1647
+ inspect,
1648
+ printOnly
1649
+ }, table.id))
1650
+ ]
1651
+ }, section.id)),
1652
+ play.runs && play.runs.length > 0 && /* @__PURE__ */ jsx(Runs, {
1653
+ runs: play.runs,
1654
+ inspect,
1655
+ printOnly,
1656
+ id: anchor("runs")
1657
+ }),
1658
+ play.rules && /* @__PURE__ */ jsx(Rules, {
1659
+ rules: play.rules,
1660
+ inspect,
1661
+ printOnly,
1662
+ id: anchor("anomalies")
1663
+ }),
1664
+ play.unknowns && /* @__PURE__ */ jsx(Unknowns, {
1665
+ unknowns: play.unknowns,
1666
+ id: anchor("unknowns")
1667
+ }),
1668
+ play.observations.length > 0 && /* @__PURE__ */ jsxs("details", {
1669
+ className: "research-observations",
1670
+ open: printOnly || void 0,
1671
+ children: [/* @__PURE__ */ jsx("summary", { children: "Checks and next steps" }), play.observations.map((item) => /* @__PURE__ */ jsxs("article", { children: [
1672
+ /* @__PURE__ */ jsx("h3", { children: item.title }),
1673
+ /* @__PURE__ */ jsx("p", { children: item.body }),
1674
+ item.evidence.length > 0 && /* @__PURE__ */ jsx(Sources, {
1675
+ sources: item.evidence,
1676
+ inspect,
1677
+ printOnly
1678
+ })
1679
+ ] }, item.id))]
1680
+ }),
1681
+ /* @__PURE__ */ jsxs("details", {
1682
+ className: "research-measurements",
1683
+ open: printOnly || void 0,
1684
+ children: [
1685
+ /* @__PURE__ */ jsx("summary", { children: "Measurements and coverage" }),
1686
+ play.metrics.some((metric) => !metric.section) && /* @__PURE__ */ jsx("dl", {
1687
+ className: "rr-metrics",
1688
+ "aria-label": "Recorded measurements",
1689
+ children: play.metrics.filter((metric) => !metric.section).map((metric) => /* @__PURE__ */ jsx(Fact, {
1690
+ metric,
1691
+ inspect,
1692
+ printOnly
1693
+ }, metric.id))
1694
+ }),
1695
+ play.limitations.length > 0 && /* @__PURE__ */ jsx("ul", { children: play.limitations.map((item, i) => /* @__PURE__ */ jsx("li", { children: item }, i)) }),
1696
+ !play.record && /* @__PURE__ */ jsx("p", { children: "No event record was supplied for this play. Activity and topology are unavailable." }),
1697
+ play.sources.length > 0 && /* @__PURE__ */ jsx(Sources, {
1698
+ sources: play.sources,
1699
+ inspect,
1700
+ printOnly
1701
+ })
1702
+ ]
1703
+ })
1704
+ ]
1705
+ }),
1706
+ !printOnly && view === "sources" && /* @__PURE__ */ jsx(KnowledgeBrowser, {
1707
+ documents: play.documents,
1708
+ selection: documentSelection,
1709
+ onSelect: openDocument,
1710
+ onClear: () => {
1711
+ setDocumentSelection(void 0);
1712
+ show("sources");
1713
+ },
1714
+ playId: play.id,
1715
+ sourceSearchEndpoint
1716
+ }),
1717
+ !printOnly && view === "activity" && (play.record ? /* @__PURE__ */ jsx(AgentRecord, {
1718
+ records: [play.record],
1719
+ theme,
1720
+ selection,
1721
+ onSelectionChange: (next) => {
1722
+ setSelection(next);
1723
+ onActivityChange?.(play.id, next);
1724
+ }
1725
+ }) : /* @__PURE__ */ jsx("p", {
1726
+ className: "chat-empty",
1727
+ children: "No event record was supplied for this play. Its activity and topology are unavailable."
1728
+ }))
1729
+ ]
1730
+ })
1731
+ });
1732
+ }
1733
+ function Questions({ questions, inspect, printOnly, open = false, id }) {
1734
+ return /* @__PURE__ */ jsxs("details", {
1735
+ className: "rr-questions",
1736
+ id,
1737
+ open: printOnly || open || void 0,
1738
+ children: [/* @__PURE__ */ jsx("summary", { children: "Questions and answers" }), /* @__PURE__ */ jsx("dl", { children: questions.map((item) => /* @__PURE__ */ jsxs("div", {
1739
+ className: "rr-question",
1740
+ children: [
1741
+ /* @__PURE__ */ jsx("dt", { children: item.question }),
1742
+ /* @__PURE__ */ jsx("dd", {
1743
+ className: "rr-meta",
1744
+ children: item.status.replace(/_/g, " ")
1745
+ }),
1746
+ item.answer ? /* @__PURE__ */ jsx("dd", { children: item.answer }) : item.status !== "observed" && /* @__PURE__ */ jsx("dd", {
1747
+ className: "rr-unknown",
1748
+ children: "Unknown"
1749
+ }),
1750
+ item.why && /* @__PURE__ */ jsxs("dd", {
1751
+ className: "rr-meta",
1752
+ children: [item.status === "observed" ? "Not known: " : "Why: ", item.why]
1753
+ }),
1754
+ item.record && /* @__PURE__ */ jsxs("dd", {
1755
+ className: "rr-meta",
1756
+ children: ["Would be answered by: ", item.record]
1757
+ }),
1758
+ item.evidence.length > 0 && /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(Sources, {
1759
+ sources: item.evidence,
1760
+ inspect,
1761
+ printOnly,
1762
+ inline: true
1763
+ }) })
1764
+ ]
1765
+ }, item.id)) })]
1766
+ });
1767
+ }
1768
+ function Sources({ sources, inspect, printOnly, inline = false }) {
1769
+ const documents = useContext(DocumentContext);
1770
+ if (inline && sources.every((source) => !source.excerpt && !source.eventId)) return /* @__PURE__ */ jsx("ul", {
1771
+ className: "rr-source-line",
1772
+ children: sources.map((source, i) => /* @__PURE__ */ jsxs("li", { children: [
1773
+ /* @__PURE__ */ jsx("span", { children: "Source " }),
1774
+ source.label && /* @__PURE__ */ jsxs("strong", { children: [source.label, " "] }),
1775
+ source.url && /* @__PURE__ */ jsx("a", {
1776
+ href: source.url,
1777
+ target: "_blank",
1778
+ rel: "noreferrer",
1779
+ children: source.url
1780
+ }),
1781
+ source.path && /* @__PURE__ */ jsxs("code", { children: [source.path, source.line ? ":" + source.line : ""] }),
1782
+ source.asOf ? /* @__PURE__ */ jsxs("span", { children: [" · as of ", utcTime(source.asOf, true)] }) : /* @__PURE__ */ jsx("span", { children: " · as-of time not recorded" })
1783
+ ] }, i))
1784
+ });
1785
+ return /* @__PURE__ */ jsxs("details", {
1786
+ className: "rr-sources",
1787
+ open: printOnly || void 0,
1788
+ children: [/* @__PURE__ */ jsxs("summary", { children: [
1789
+ sources.length,
1790
+ " ",
1791
+ sources.length === 1 ? "source" : "sources"
1792
+ ] }), /* @__PURE__ */ jsx("ol", { children: sources.map((source, i) => /* @__PURE__ */ jsxs("li", { children: [
1793
+ source.label && /* @__PURE__ */ jsx("strong", { children: source.label }),
1794
+ source.url && /* @__PURE__ */ jsx("a", {
1795
+ href: source.url,
1796
+ target: "_blank",
1797
+ rel: "noreferrer",
1798
+ children: source.url
1799
+ }),
1800
+ source.path && /* @__PURE__ */ jsxs("code", { children: [source.path, source.line ? ":" + source.line : ""] }),
1801
+ !printOnly && source.path && resolveDocument(documents.documents, source.path) && /* @__PURE__ */ jsx("button", {
1802
+ className: "ui-button",
1803
+ type: "button",
1804
+ onClick: () => documents.open({
1805
+ path: resolveDocument(documents.documents, source.path).path,
1806
+ line: source.line
1807
+ }),
1808
+ children: "Read retained document"
1809
+ }),
1810
+ source.sha256 && /* @__PURE__ */ jsxs("small", { children: ["SHA-256 ", /* @__PURE__ */ jsx("code", { children: source.sha256 })] }),
1811
+ source.asOf && /* @__PURE__ */ jsxs("small", { children: ["As of ", utcTime(source.asOf, true)] }),
1812
+ source.excerpt && /* @__PURE__ */ jsx("blockquote", { children: source.excerpt }),
1813
+ source.eventId && (printOnly ? /* @__PURE__ */ jsxs("code", { children: ["Event: ", source.eventId] }) : /* @__PURE__ */ jsx("button", {
1814
+ className: "ui-button",
1815
+ type: "button",
1816
+ onClick: () => inspect(source.eventId),
1817
+ children: "Open recorded event"
1818
+ }))
1819
+ ] }, i)) })]
1820
+ });
1821
+ }
1822
+ /** One fact: its value with unit, or Unknown with why and the record that would answer it, then its source. */
1823
+ function Fact({ metric, inspect, printOnly }) {
1824
+ const mark = metric.comparable === false ? "not comparable" : metric.comparable === true ? "comparable" : null;
1825
+ const note = mark && metric.note ? metric.note.replace(/^not comparable:\s*/i, "") : metric.note;
1826
+ return /* @__PURE__ */ jsxs("div", {
1827
+ "data-fact": metric.id,
1828
+ "data-unknown": metric.value === null || void 0,
1829
+ children: [
1830
+ /* @__PURE__ */ jsx("dt", { children: metric.label }),
1831
+ /* @__PURE__ */ jsxs("dd", {
1832
+ className: metric.value === null ? "rr-unknown" : void 0,
1833
+ children: [metric.value ?? "Unknown", metric.value !== null && metric.unit ? ` ${metric.unit}` : ""]
1834
+ }),
1835
+ mark && /* @__PURE__ */ jsx("small", {
1836
+ className: "rr-comparable",
1837
+ "data-comparable": String(metric.comparable),
1838
+ children: mark
1839
+ }),
1840
+ note && /* @__PURE__ */ jsx("small", { children: note }),
1841
+ metric.coverage && /* @__PURE__ */ jsx("small", { children: metric.coverage }),
1842
+ metric.value === null && metric.why && /* @__PURE__ */ jsxs("small", { children: ["Why: ", metric.why] }),
1843
+ metric.value === null && metric.record && /* @__PURE__ */ jsxs("small", { children: ["Would be answered by: ", metric.record] }),
1844
+ metric.source && /* @__PURE__ */ jsx(Sources, {
1845
+ sources: [metric.source],
1846
+ inspect,
1847
+ printOnly,
1848
+ inline: true
1849
+ })
1850
+ ]
1851
+ });
1852
+ }
1853
+ function Table({ table, inspect, printOnly }) {
1854
+ const marked = table.rows.some((row) => row.some((cell) => cell.comparable === false));
1855
+ return /* @__PURE__ */ jsxs("figure", {
1856
+ className: "rr-data-table",
1857
+ "data-table": table.id,
1858
+ children: [
1859
+ /* @__PURE__ */ jsxs("figcaption", { children: [table.title, marked && /* @__PURE__ */ jsx("span", {
1860
+ className: "small",
1861
+ children: " · * not comparable with the bar"
1862
+ })] }),
1863
+ /* @__PURE__ */ jsx("div", {
1864
+ className: "rr-table-scroll",
1865
+ children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: table.columns.map((column) => /* @__PURE__ */ jsx("th", {
1866
+ scope: "col",
1867
+ children: column
1868
+ }, column)) }) }), /* @__PURE__ */ jsx("tbody", { children: table.rows.map((row, i) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsxs("td", {
1869
+ className: cell.text === null ? "rr-unknown" : void 0,
1870
+ title: cell.note,
1871
+ children: [
1872
+ cell.text === null ? "unknown" : cell.href ? /* @__PURE__ */ jsx("a", {
1873
+ href: cell.href,
1874
+ children: cell.text
1875
+ }) : cell.text,
1876
+ cell.comparable === false && /* @__PURE__ */ jsx("span", {
1877
+ className: "rr-comparable",
1878
+ "data-comparable": "false",
1879
+ children: "*"
1880
+ }),
1881
+ cell.note && /* @__PURE__ */ jsx("small", { children: cell.note })
1882
+ ]
1883
+ }, j)) }, i)) })] })
1884
+ }),
1885
+ table.source && /* @__PURE__ */ jsx(Sources, {
1886
+ sources: [table.source],
1887
+ inspect,
1888
+ printOnly,
1889
+ inline: true
1890
+ })
1891
+ ]
1892
+ });
1893
+ }
1894
+ /** Each run: its ending, agent-hours stacked by attempt outcome on one shared axis (the play ledger's bar), and its page. */
1895
+ function Runs({ runs, inspect, printOnly, id }) {
1896
+ const total = (run) => Object.values(run.hoursByOutcome ?? {}).reduce((sum, value) => sum + value, 0);
1897
+ const max = Math.max(1, ...runs.map(total));
1898
+ const present = OUTCOME_ORDER.filter((outcome) => runs.some((run) => (run.hoursByOutcome?.[outcome] ?? 0) > 0));
1899
+ return /* @__PURE__ */ jsxs("section", {
1900
+ className: "rr-section",
1901
+ id,
1902
+ "data-section": "runs",
1903
+ "aria-label": "Runs",
1904
+ children: [
1905
+ /* @__PURE__ */ jsx("h3", { children: "Runs" }),
1906
+ present.length > 0 && /* @__PURE__ */ jsx(OutcomeLegend, { outcomes: present }),
1907
+ /* @__PURE__ */ jsx("ol", {
1908
+ className: "rr-runs",
1909
+ children: runs.map((run) => /* @__PURE__ */ jsxs("li", {
1910
+ className: "rr-run",
1911
+ "data-run": run.id,
1912
+ children: [
1913
+ /* @__PURE__ */ jsxs("p", {
1914
+ className: "rr-run-head",
1915
+ children: [
1916
+ run.href ? /* @__PURE__ */ jsx("a", {
1917
+ href: run.href,
1918
+ children: run.label
1919
+ }) : /* @__PURE__ */ jsx("strong", { children: run.label }),
1920
+ /* @__PURE__ */ jsx("span", { children: run.ending ?? "ending unknown" }),
1921
+ run.best && /* @__PURE__ */ jsxs("span", { children: ["best ", run.best] }),
1922
+ /* @__PURE__ */ jsx("span", { children: run.cost ?? "cost unknown" }),
1923
+ /* @__PURE__ */ jsx("span", { children: run.wallHours === null ? "wall time unknown" : `${run.wallHours} h wall` })
1924
+ ]
1925
+ }),
1926
+ run.hoursByOutcome && total(run) > 0 ? /* @__PURE__ */ jsx(OutcomeBar, {
1927
+ row: {
1928
+ hoursByOutcome: run.hoursByOutcome,
1929
+ triesByOutcome: run.triesByOutcome ?? null
1930
+ },
1931
+ max
1932
+ }) : /* @__PURE__ */ jsx("dl", {
1933
+ className: "rr-metrics rr-fact-grid",
1934
+ children: /* @__PURE__ */ jsx(Fact, {
1935
+ metric: run.agentHours,
1936
+ inspect,
1937
+ printOnly
1938
+ })
1939
+ }),
1940
+ run.hoursByOutcome && total(run) > 0 && run.agentHours.source && /* @__PURE__ */ jsx(Sources, {
1941
+ sources: [run.agentHours.source],
1942
+ inspect,
1943
+ printOnly,
1944
+ inline: true
1945
+ }),
1946
+ run.reason && /* @__PURE__ */ jsx("p", {
1947
+ className: "small",
1948
+ children: run.reason
1949
+ })
1950
+ ]
1951
+ }, run.id))
1952
+ })
1953
+ ]
1954
+ });
1955
+ }
1956
+ const RULE_STATE = {
1957
+ fired: "fired",
1958
+ quiet: "quiet",
1959
+ "could-not-evaluate": "could not evaluate"
1960
+ };
1961
+ /** What looks wrong: every rule with its state, and each instance it fired on with the sentence and source that say why. */
1962
+ function Rules({ rules, inspect, printOnly, id }) {
1963
+ const fired = rules.reduce((sum, rule) => sum + rule.instances.length, 0);
1964
+ return /* @__PURE__ */ jsxs("section", {
1965
+ className: "rr-section",
1966
+ id,
1967
+ "data-section": "anomalies",
1968
+ "aria-label": "Anomalies",
1969
+ children: [
1970
+ /* @__PURE__ */ jsx("h3", { children: "Anomalies" }),
1971
+ /* @__PURE__ */ jsxs("p", { children: [
1972
+ fired,
1973
+ " ",
1974
+ fired === 1 ? "anomaly" : "anomalies",
1975
+ " from ",
1976
+ rules.filter((rule) => rule.instances.length).length,
1977
+ " of ",
1978
+ rules.length,
1979
+ " rules."
1980
+ ] }),
1981
+ !rules.length && /* @__PURE__ */ jsx("p", {
1982
+ className: "chat-empty",
1983
+ children: "No rule was evaluated."
1984
+ }),
1985
+ /* @__PURE__ */ jsx("ol", {
1986
+ className: "rr-rules",
1987
+ children: rules.map((rule) => /* @__PURE__ */ jsxs("li", {
1988
+ className: "rr-rule",
1989
+ "data-rule": rule.id,
1990
+ "data-state": rule.state,
1991
+ children: [
1992
+ /* @__PURE__ */ jsxs("p", {
1993
+ className: "rr-rule-head",
1994
+ children: [
1995
+ /* @__PURE__ */ jsx("span", {
1996
+ className: "rr-assessment",
1997
+ "data-rule-state": RULE_STATE[rule.state] ? rule.state : "unrecognised",
1998
+ children: RULE_STATE[rule.state] ?? `unrecognised state: ${rule.state}`
1999
+ }),
2000
+ " ",
2001
+ /* @__PURE__ */ jsx("strong", { children: rule.question })
2002
+ ]
2003
+ }),
2004
+ /* @__PURE__ */ jsxs("p", {
2005
+ className: "rr-meta",
2006
+ children: [
2007
+ /* @__PURE__ */ jsx("code", { children: rule.id }),
2008
+ rule.threshold && /* @__PURE__ */ jsxs(Fragment, { children: [" · threshold ", rule.threshold] }),
2009
+ " · ",
2010
+ rule.instances.length,
2011
+ " fired"
2012
+ ]
2013
+ }),
2014
+ rule.instances.length > 0 && /* @__PURE__ */ jsx("ul", { children: rule.instances.map((instance, i) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsxs("p", { children: [instance.sentence, instance.value && /* @__PURE__ */ jsxs("span", {
2015
+ className: "rr-meta",
2016
+ children: [
2017
+ " (",
2018
+ instance.value,
2019
+ ")"
2020
+ ]
2021
+ })] }), instance.evidence.length ? /* @__PURE__ */ jsx(Sources, {
2022
+ sources: instance.evidence,
2023
+ inspect,
2024
+ printOnly,
2025
+ inline: true
2026
+ }) : /* @__PURE__ */ jsx("p", {
2027
+ className: "rr-meta",
2028
+ children: "Source not recorded."
2029
+ })] }, i)) })
2030
+ ]
2031
+ }, rule.id))
2032
+ })
2033
+ ]
2034
+ });
2035
+ }
2036
+ function Unknowns({ unknowns, id }) {
2037
+ return /* @__PURE__ */ jsxs("section", {
2038
+ className: "rr-section",
2039
+ id,
2040
+ "data-section": "unknowns",
2041
+ "aria-label": "Unknowns",
2042
+ children: [/* @__PURE__ */ jsx("h3", { children: "Unknowns" }), !unknowns.length ? /* @__PURE__ */ jsx("p", { children: "The report lists no unknown field." }) : /* @__PURE__ */ jsx("ul", {
2043
+ className: "rr-unknowns",
2044
+ children: unknowns.map((item, i) => /* @__PURE__ */ jsxs("li", {
2045
+ "data-unknown-field": item.field,
2046
+ children: [
2047
+ /* @__PURE__ */ jsx("code", { children: item.field }),
2048
+ " ",
2049
+ /* @__PURE__ */ jsx("span", {
2050
+ className: "rr-unknown",
2051
+ children: "unknown"
2052
+ }),
2053
+ /* @__PURE__ */ jsxs("p", {
2054
+ className: "rr-meta",
2055
+ children: ["Why: ", item.why]
2056
+ }),
2057
+ /* @__PURE__ */ jsxs("p", {
2058
+ className: "rr-meta",
2059
+ children: ["Would be answered by: ", item.record]
2060
+ })
2061
+ ]
2062
+ }, i))
2063
+ })]
2064
+ });
2065
+ }
2066
+ // src/record/WorkGraph.tsx
2067
+ function RecordCard({ data }) {
2068
+ const node = data.record;
2069
+ return /* @__PURE__ */ jsxs("div", {
2070
+ title: node.title,
2071
+ className: `graph-node kind-${node.kind}`,
2072
+ children: [
2073
+ /* @__PURE__ */ jsx(Handle, {
2074
+ type: "target",
2075
+ position: Position.Left,
2076
+ className: "opacity-0"
2077
+ }),
2078
+ /* @__PURE__ */ jsx("span", {
2079
+ className: "graph-node-title",
2080
+ children: node.title
2081
+ }),
2082
+ /* @__PURE__ */ jsx("span", {
2083
+ className: "graph-node-sub",
2084
+ children: node.sub
2085
+ }),
2086
+ /* @__PURE__ */ jsx(Handle, {
2087
+ type: "source",
2088
+ position: Position.Right,
2089
+ className: "opacity-0"
2090
+ })
2091
+ ]
2092
+ });
2093
+ }
2094
+ const nodeTypes = { record: RecordCard };
2095
+ /** Recorded evidence relationships keep their domain kinds; the maintained graph engine owns the camera and focus. */
2096
+ function RecordWorkGraph({ record }) {
2097
+ const graph = useMemo(() => recordWorkGraphModel(record), [record]);
2098
+ const wrapperRef = useRef(null);
2099
+ const colorMode = useColorMode(wrapperRef, graph.nodes.length > 0);
2100
+ const [selected, setSelected] = useState(null);
2101
+ const chosen = selected ? graph.byId.get(selected) : void 0;
2102
+ const adjacent = new Set(chosen ? [chosen.id, ...graph.edges.filter((edge) => edge.from === chosen.id || edge.to === chosen.id).flatMap((edge) => [edge.from, edge.to])] : []);
2103
+ const nodes = useMemo(() => graph.nodes.map((node) => ({
2104
+ id: node.id,
2105
+ type: "record",
2106
+ position: {
2107
+ x: node.x,
2108
+ y: node.y
2109
+ },
2110
+ data: { record: node },
2111
+ width: NODE_W,
2112
+ height: NODE_H,
2113
+ ariaLabel: `${node.kind}: ${node.title}`,
2114
+ domAttributes: {
2115
+ "data-graph-node-id": node.id,
2116
+ "data-graph-node-kind": node.kind,
2117
+ "aria-pressed": node.id === selected
2118
+ },
2119
+ selected: node.id === selected,
2120
+ style: { opacity: selected && !adjacent.has(node.id) ? .35 : 1 }
2121
+ })), [graph, selected]);
2122
+ const edges = useMemo(() => graph.edges.map((edge, i) => ({
2123
+ id: String(i),
2124
+ source: edge.from,
2125
+ target: edge.to,
2126
+ label: edge.kind,
2127
+ markerEnd: { type: MarkerType.ArrowClosed },
2128
+ className: `edge-${edge.kind}`,
2129
+ style: { opacity: selected && edge.from !== selected && edge.to !== selected ? .2 : 1 },
2130
+ data: { kind: edge.kind }
2131
+ })), [graph, selected]);
2132
+ return /* @__PURE__ */ jsx("div", {
2133
+ ref: wrapperRef,
2134
+ className: "agent-record ar-ws",
2135
+ children: /* @__PURE__ */ jsxs("section", {
2136
+ className: "work-graph",
2137
+ "aria-label": graph.title,
2138
+ "data-work-graph": graph.nodes.length,
2139
+ "data-graph-kind": "record",
2140
+ children: [/* @__PURE__ */ jsxs("div", {
2141
+ className: "graph-main",
2142
+ children: [
2143
+ /* @__PURE__ */ jsxs("header", {
2144
+ className: "graph-head",
2145
+ children: [/* @__PURE__ */ jsx("h2", { children: graph.title }), /* @__PURE__ */ jsx("span", {
2146
+ className: "faint",
2147
+ children: graph.summary
2148
+ })]
2149
+ }),
2150
+ /* @__PURE__ */ jsx("div", {
2151
+ className: "graph-viewport",
2152
+ style: { height: 560 },
2153
+ onKeyDownCapture: (event) => {
2154
+ const target = event.target instanceof Element ? event.target.closest(".react-flow__node[data-id]") : null;
2155
+ const from = target?.dataset.id ? graph.byId.get(target.dataset.id) : void 0;
2156
+ if (!from) return;
2157
+ if (event.key === "Enter" || event.key === " ") {
2158
+ event.preventDefault();
2159
+ event.stopPropagation();
2160
+ setSelected(selected === from.id ? null : from.id);
2161
+ return;
2162
+ }
2163
+ if (![
2164
+ "ArrowLeft",
2165
+ "ArrowRight",
2166
+ "ArrowUp",
2167
+ "ArrowDown"
2168
+ ].includes(event.key)) return;
2169
+ const horizontal = event.key === "ArrowLeft" || event.key === "ArrowRight", sign = event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 1;
2170
+ const score = (node) => horizontal ? Math.abs(node.y - from.y) * 4 + Math.abs(node.x - from.x) : Math.abs(node.x - from.x) * 4 + Math.abs(node.y - from.y);
2171
+ const next = graph.nodes.filter((node) => node.id !== from.id && (horizontal ? node.x - from.x : node.y - from.y) * sign > 0).sort((a, b) => score(a) - score(b) || a.id.localeCompare(b.id))[0];
2172
+ if (next) {
2173
+ event.preventDefault();
2174
+ event.stopPropagation();
2175
+ event.currentTarget.querySelector(`.react-flow__node[data-id="${CSS.escape(next.id)}"]`)?.focus();
2176
+ }
2177
+ },
2178
+ children: /* @__PURE__ */ jsx(ReactFlow, {
2179
+ colorMode,
2180
+ nodes,
2181
+ edges,
2182
+ nodeTypes,
2183
+ fitView: true,
2184
+ nodesDraggable: false,
2185
+ nodesConnectable: false,
2186
+ minZoom: .12,
2187
+ maxZoom: 2,
2188
+ onNodeClick: (_, node) => setSelected(selected === node.id ? null : node.id),
2189
+ onPaneClick: () => setSelected(null),
2190
+ children: /* @__PURE__ */ jsx(Controls, { showInteractive: false })
2191
+ }, record.runId)
2192
+ }),
2193
+ Object.keys(graph.hidden).length > 0 && /* @__PURE__ */ jsxs("p", {
2194
+ className: "faint",
2195
+ children: [
2196
+ "Each lane shows its first ",
2197
+ PER_LANE,
2198
+ " results, claims and checks; ",
2199
+ Object.values(graph.hidden).reduce((a, b) => a + b, 0),
2200
+ " more are retained in the record."
2201
+ ]
2202
+ }),
2203
+ /* @__PURE__ */ jsx("ul", {
2204
+ className: "graph-legend",
2205
+ children: graph.legend.map((entry) => /* @__PURE__ */ jsx("li", { children: entry.label }, entry.kind))
2206
+ })
2207
+ ]
2208
+ }), /* @__PURE__ */ jsx("aside", {
2209
+ className: "graph-side finding-card",
2210
+ "aria-label": "Selected card",
2211
+ children: chosen ? /* @__PURE__ */ jsxs(Fragment, { children: [
2212
+ /* @__PURE__ */ jsx("h3", { children: chosen.kind }),
2213
+ /* @__PURE__ */ jsx("div", {
2214
+ className: "graph-side-title",
2215
+ children: chosen.title
2216
+ }),
2217
+ /* @__PURE__ */ jsx("div", {
2218
+ className: "faint",
2219
+ children: chosen.sub
2220
+ }),
2221
+ chosen.summary && /* @__PURE__ */ jsx("p", { children: chosen.summary }),
2222
+ /* @__PURE__ */ jsxs("div", {
2223
+ className: "faint",
2224
+ children: [
2225
+ graph.edges.filter((edge) => edge.from === chosen.id).length,
2226
+ " links out · ",
2227
+ graph.edges.filter((edge) => edge.to === chosen.id).length,
2228
+ " links in"
2229
+ ]
2230
+ }),
2231
+ chosen.href && /* @__PURE__ */ jsx("a", {
2232
+ href: chosen.href,
2233
+ target: "_blank",
2234
+ rel: "noopener noreferrer",
2235
+ children: "Open source ↗"
2236
+ })
2237
+ ] }) : /* @__PURE__ */ jsx("p", {
2238
+ className: "faint",
2239
+ children: "Select a card to see its recorded relationships and details."
2240
+ })
2241
+ })]
2242
+ })
2243
+ });
2244
+ }
2245
+ // src/record/selection.ts
2246
+ /** URL selections only name documents already supplied within the exact play. */
2247
+ function readReportLocation(report, search) {
2248
+ const params = new URLSearchParams(search);
2249
+ if ([
2250
+ "play",
2251
+ "document",
2252
+ "line",
2253
+ "view",
2254
+ "agent",
2255
+ "event"
2256
+ ].some((key) => params.getAll(key).length > 1)) return { error: "This citation contains an ambiguous selection." };
2257
+ const viewText = params.get("view");
2258
+ if (viewText !== null && ![
2259
+ "results",
2260
+ "activity",
2261
+ "sources"
2262
+ ].includes(viewText)) return { error: "Unknown research view." };
2263
+ const view = viewText;
2264
+ const viewOption = view ? { view } : {};
2265
+ const playId = params.get("play") ?? void 0;
2266
+ const path = params.get("document") ?? void 0;
2267
+ const lineText = params.get("line") ?? void 0;
2268
+ const agentId = params.get("agent") ?? void 0;
2269
+ const eventId = params.get("event") ?? void 0;
2270
+ if (!playId && (path !== void 0 || lineText !== void 0 || agentId !== void 0 || eventId !== void 0)) return { error: "A document citation must name its play." };
2271
+ if (!playId) return viewOption;
2272
+ const play = report.plays.find((item) => item.id === playId);
2273
+ if (!play) return { error: "The cited play is not included in this report." };
2274
+ if (agentId !== void 0 || eventId !== void 0) {
2275
+ if (path !== void 0 || lineText !== void 0 || !play.record) return { error: "Activity citation does not match this play." };
2276
+ const node = agentId ? play.record.nodes.find((item) => item.id === agentId) : void 0;
2277
+ const event = eventId ? play.record.events.find((item) => item.id === eventId) : void 0;
2278
+ if (agentId && !node || eventId && !event) return { error: "This activity is not retained in the selected play." };
2279
+ if (event && node) {
2280
+ const observed = play.record.nodes.find((item) => item.id === event.node);
2281
+ if (event.node !== node.id && observed?.agentId !== node.id) return { error: "The event belongs to a different agent." };
2282
+ }
2283
+ return {
2284
+ playId,
2285
+ view: "activity",
2286
+ activity: {
2287
+ runId: play.record.runId,
2288
+ ...agentId ? { nodeId: agentId } : {},
2289
+ ...eventId ? { eventId } : {}
2290
+ }
2291
+ };
2292
+ }
2293
+ if (path === void 0) return lineText === void 0 ? {
2294
+ playId,
2295
+ ...viewOption
2296
+ } : { error: "A line citation must name its document." };
2297
+ if (!play.documents.some((item) => item.path === path)) return { error: "The cited document is not retained in this play." };
2298
+ const line = lineText === void 0 ? void 0 : Number(lineText);
2299
+ if (line !== void 0 && (!/^[1-9]\d*$/.test(lineText) || !Number.isSafeInteger(line))) return { error: "The cited source line must be a positive whole number." };
2300
+ return {
2301
+ playId,
2302
+ ...viewOption,
2303
+ document: {
2304
+ playId,
2305
+ path,
2306
+ ...line === void 0 ? {} : { line }
2307
+ }
2308
+ };
2309
+ }
2310
+ function reportLocationSearch(search, playId, document, view, activity) {
2311
+ const params = new URLSearchParams(search);
2312
+ params.set("play", playId);
2313
+ params.delete("document");
2314
+ params.delete("line");
2315
+ params.delete("view");
2316
+ params.delete("agent");
2317
+ params.delete("event");
2318
+ if (activity) {
2319
+ if (activity.nodeId) params.set("agent", activity.nodeId);
2320
+ if (activity.eventId) params.set("event", activity.eventId);
2321
+ }
2322
+ if (view) params.set("view", view);
2323
+ if (document && document.playId === playId) {
2324
+ params.set("document", document.path);
2325
+ if (document.line !== void 0) params.set("line", String(document.line));
2326
+ }
2327
+ return "?" + params.toString();
2328
+ }
2329
+ export { AgentRecord, RecordWorkGraph, ResearchReport, readReportLocation, reportLocationSearch };