@tangle-network/sandbox-ui 2.0.21 → 3.0.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.
Files changed (40) hide show
  1. package/README.md +23 -0
  2. package/dist/{record-CnxPRyxC.js → AgentTree-CJRVpgPq.js} +196 -83
  3. package/dist/ResearchReport-CSNpZ-pj.js +2063 -0
  4. package/dist/ResearchReport-CmFYqNVY.d.ts +36 -0
  5. package/dist/{WorkflowGraph-CADReIDl.js → WorkflowGraph-B8cRtVaw.js} +2 -2
  6. package/dist/{chat-BU39pb2j.js → chat-BrlLa4rg.js} +2 -2
  7. package/dist/chat.d.ts +1 -1
  8. package/dist/chat.js +1 -1
  9. package/dist/globals.css +5837 -419
  10. package/dist/hooks.d.ts +2 -2
  11. package/dist/hooks.js +2 -2
  12. package/dist/{index-Due7tY0w.d.ts → index-5zOVKgDT.d.ts} +2 -2
  13. package/dist/{index-DxMX-DlX.d.ts → index-YU_b9xCK.d.ts} +3 -3
  14. package/dist/{index-1qlc9-S2.d.ts → index-gXcuwPOX.d.ts} +5 -6
  15. package/dist/index.d.ts +5 -5
  16. package/dist/index.js +7 -6
  17. package/dist/record/ResearchReport.d.ts +2 -0
  18. package/dist/record/ResearchReport.js +1 -0
  19. package/dist/record-Ct_MZeZC.js +47 -0
  20. package/dist/record.d.ts +33 -0
  21. package/dist/record.js +270 -0
  22. package/dist/report-viewer.js +554 -0
  23. package/dist/run/live.js +14 -33
  24. package/dist/run/record.d.ts +2 -2
  25. package/dist/run/record.js +3 -2
  26. package/dist/run.d.ts +3 -3
  27. package/dist/run.js +5 -4
  28. package/dist/runtime-viewer/{AgentGraphCanvas-B74GN1o6-DC3EH_ss.js → AgentGraphCanvas-CcBWMidO-DIW8tof6.js} +1366 -1348
  29. package/dist/runtime-viewer/{latest-events-iiWwpIck-BTK0vSlR.js → latest-events-BfRc-7l9-QhJD89rn.js} +899 -748
  30. package/dist/runtime-viewer/sandbox-ui-CUj4Q1P_.css +2 -0
  31. package/dist/styles.css +5838 -420
  32. package/dist/tailwind.css +5677 -5
  33. package/dist/tokens.css +7 -1
  34. package/dist/{trace-workspace-DkakKRLV.js → trace-workspace-BiJbv9WQ.js} +2 -2
  35. package/dist/use-follow-tail-Ht7fZGQQ.js +1 -0
  36. package/dist/workflows.js +1 -1
  37. package/dist/workspace-viewer.js +8785 -11045
  38. package/package.json +30 -12
  39. package/dist/runtime-viewer/sandbox-ui-z5RGk327.css +0 -2
  40. package/dist/use-follow-tail-Ckx5ZSV2.js +0 -1
package/README.md CHANGED
@@ -26,6 +26,12 @@ declarations. Interface 4.x, 5.0.x, and 5.1.x are not admitted. New majors requi
26
26
  qualification before the peer range is extended.
27
27
  Optional peers are required only by the subpaths that use them; see [package.json](./package.json). `/editor` needs its tiptap, `yjs` and `@hocuspocus/provider` peers only when it renders an editor, and the `@tangle-network/ui` README holds the table of which surface needs which. `/files` needs `pdfjs-dist` only for `PdfViewer`, which `FilePreview` uses for PDFs; without it the viewer says so and falls back to the browser's own PDF viewer. The `ui` README covers its worker and asset setup through `configurePdfViewer`.
28
28
 
29
+ ### Retained reports
30
+
31
+ Import `ResearchReport` from `@tangle-network/sandbox-ui/record/ResearchReport` for report-only server rendering. It needs the Core report/data peer and React; it avoids the optional WorkGraph feature and chat session stores. `record` retains the complete record/report/WorkGraph surface for hosts that use those features. Load the shared stylesheet for both.
32
+
33
+ The Charts and UI peers in [package.json](./package.json) provide the shared clock and canonical run statuses; graph producers send `done` for completed work. Standalone report assets bundle the same UTC timestamp implementation during release.
34
+
29
35
  ### Retained conversations
30
36
 
31
37
  `Conversation`, `AgentTree`, `indexRecord` and `withPrompts` from `@tangle-network/sandbox-ui/run/record` show retained conversations and their recorded hierarchy. `AgentTree` takes `index`, selected `actor`, recorded-time `cutoff` and `select(id)`. Load the shared stylesheet and pass validated presentation data. Original source IDs, reconstructed prompts, pending tool receipts, collapsed idle polls, source callbacks and assessment flags remain available.
@@ -531,3 +537,20 @@ Retheming is absolutely supported, but the documentation was thinner than it sho
531
537
  Apache-2.0
532
538
 
533
539
  The native Runtime viewer is `LiveRun` from `@tangle-network/sandbox-ui/run/live`. It composes the same trace workspace, selected-agent conversation, retained files and capture coverage. Hosts may supply the existing fetch/EventSource transport for their authenticated session; an API or authenticated transport change clears the previous view, while frames within one stable transport preserve selection and the camera. Keep the transport object stable for that account. `@tangle-network/sandbox-ui/workspace-viewer.js` is the self-contained browser entry for Runtime's existing asset-directory contract, with `styles.css` beside it. `useDocument` owns same-origin ETag reads. `useEventStream` from `/run` owns native reconnect, terminal end and stale callback cleanup; `useAgentStream` resumes retained rows by generation and sequence. Authenticated openers keep their session and reconnect policy. Producers own validation and accounting.
540
+
541
+ ## Retained records and reports
542
+
543
+ `@tangle-network/sandbox-ui/record` owns `AgentRecord`, `ResearchReport` and `RecordWorkGraph`. Install the optional `@drewstone/agent-record` peer for this feature; its public `/record` and `/report` entries own validation, models, provenance relationships and report exports. The generic `/run/record` transcript remains usable without that parser package.
544
+
545
+ ```tsx
546
+ import { parseRecord } from '@drewstone/agent-record/record'
547
+ import { AgentRecord } from '@tangle-network/sandbox-ui/record'
548
+ import '@tangle-network/sandbox-ui/styles'
549
+
550
+ const record = parseRecord(input)
551
+ <AgentRecord records={[record]} />
552
+ ```
553
+
554
+ Selection keeps original run, actor and event IDs, a recorded timestamp, and Messages, Event details or Usage. Charts owns the waterfall clock, playback and zoom. Usage distinguishes missing counters from zero and metered windows from individual responses. Capture gaps and original source references stay visible. The evidence graph keeps recorded entity kinds and relationships and uses the maintained graph camera.
555
+
556
+ Research reports retain source documents, exact citations, Markdown/math, print and JSON/LaTeX exports. Optional source search accepts only a same-origin path and displays only the selected play's retained bytes. `agent-record-report` continues to write a single offline HTML file using this package's report viewer and stylesheet.
@@ -1,13 +1,18 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.js";
2
2
  import { t as VirtualList } from "./VirtualList-DLh2JE0F.js";
3
- import { t as useFollowTail } from "./use-follow-tail-Ckx5ZSV2.js";
3
+ import { t as useFollowTail } from "./use-follow-tail-Ht7fZGQQ.js";
4
4
  import { HoverCard, HoverCardContent, HoverCardTrigger } from "@tangle-network/ui/primitives";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
7
  import { Brain, Settings, Timer } from "lucide-react";
8
- import { formatDuration, getToolCategory, getToolDisplayMetadata } from "@tangle-network/ui/utils";
9
- import { AgentTimelineColumn, AgentTimelineStep, SpeakerHeader, agentTimelineStepKind } from "@tangle-network/ui/chat";
10
- import { ExpandedToolDetail, InlineThinkingItem, RunRowShell, RunStatusIcon, runStatus } from "@tangle-network/ui/run";
8
+ import { getToolCategory, getToolDisplayMetadata } from "@tangle-network/ui/utils";
9
+ import { AgentTimelineColumn, AgentTimelineStep, agentTimelineStepKind } from "@tangle-network/ui/chat/agent-timeline";
10
+ import { formatCompact, formatCompact as fmt, formatCost, formatSpanDuration, formatSpanDuration as interval, formatUtcTime as utcTime, reportedCount, sumReportedCounts } from "@tangle-network/charts";
11
+ import { SpeakerHeader } from "@tangle-network/ui/chat/speaker";
12
+ import { ExpandedToolDetail } from "@tangle-network/ui/run/expanded-tool-detail";
13
+ import { InlineThinkingItem } from "@tangle-network/ui/run/inline-thinking-item";
14
+ import { RunRowShell } from "@tangle-network/ui/run/run-row-shell";
15
+ import { RunStatusIcon, isOpen, runStatus, statusMeta } from "@tangle-network/ui/run/status-meta";
11
16
  // src/run/record/model.ts
12
17
  var model_exports = /* @__PURE__ */ __exportAll({
13
18
  agentState: () => agentState,
@@ -24,6 +29,7 @@ var model_exports = /* @__PURE__ */ __exportAll({
24
29
  modelIdentity: () => modelIdentity,
25
30
  ms: () => ms,
26
31
  preview: () => preview,
32
+ recordFlowTrace: () => recordFlowTrace,
27
33
  roleOf: () => roleOf,
28
34
  textOf: () => textOf,
29
35
  toolKey: () => toolKey,
@@ -31,13 +37,6 @@ var model_exports = /* @__PURE__ */ __exportAll({
31
37
  utcTime: () => utcTime
32
38
  });
33
39
  const ms = (value) => Date.parse(value);
34
- /** Normalize labels to UTC without rewriting the source timestamp. */
35
- function utcTime(value, includeDate = false) {
36
- const date = new Date(value);
37
- if (!Number.isFinite(date.valueOf())) return "Unknown time";
38
- const iso = date.toISOString();
39
- return `${includeDate ? iso.slice(0, 19).replace("T", " ") : iso.slice(11, 19)} UTC`;
40
- }
41
40
  const compact = (value) => value.replace(/\s+/g, " ").trim();
42
41
  /**
43
42
  * Recorded text names the host folders an agent ran in (`/home/<user>/.cache/disco/host/<run>/sessions/<session>/inputs/x.md`).
@@ -72,11 +71,6 @@ function withoutHostPaths(event) {
72
71
  }
73
72
  };
74
73
  }
75
- const fmt = (value) => new Intl.NumberFormat("en-US", {
76
- notation: "compact",
77
- maximumFractionDigits: 1
78
- }).format(value);
79
- const interval = (value) => value < 1e3 ? `${Math.round(value)} ms` : value < 6e4 ? `${(value / 1e3).toFixed(1)} s` : `${(value / 6e4).toFixed(1)} min`;
80
74
  const roleOf = (node) => node?.role ?? (node?.kind === "session" ? "Session" : node?.kind === "finding" ? "Finding" : "Agent");
81
75
  const textOf = (event) => event.detail.publicText ?? event.detail.recordedClaim ?? "";
82
76
  const toolKey = (node, id) => JSON.stringify([node, id]);
@@ -157,8 +151,9 @@ function indexRecord(record) {
157
151
  results.set(key, matching);
158
152
  }
159
153
  }
160
- const times = events.map((event) => ms(event.at));
161
- const start = times[0] ?? 0;
154
+ const times = [...events.map((event) => ms(event.at)), ...actors.flatMap((node) => node.start ? [ms(node.start)] : [])].filter(Number.isFinite);
155
+ const start = times.length ? times.reduce((first, at) => Math.min(first, at)) : 0;
156
+ const observedEnd = events.reduce((latest, event) => Math.max(latest, event.end ? ms(event.end) : ms(event.at)), start);
162
157
  return {
163
158
  nodes,
164
159
  canonical,
@@ -170,7 +165,7 @@ function indexRecord(record) {
170
165
  calls,
171
166
  results,
172
167
  start,
173
- end: events.reduce((latest, event) => Math.max(latest, event.end ? ms(event.end) : ms(event.at)), times.at(-1) ?? start)
168
+ end: actors.reduce((latest, node) => typeof node.end === "string" && Number.isFinite(ms(node.end)) ? Math.max(latest, ms(node.end)) : latest, observedEnd)
174
169
  };
175
170
  }
176
171
  /** Whether any event of this agent recorded token usage or a list price: a status alone measures nothing. */
@@ -282,7 +277,7 @@ function tooltipSections(event, index, cutoff) {
282
277
  const duration = event.detail.durationMs;
283
278
  if (typeof duration === "number") sections.push({
284
279
  kind: "status",
285
- value: `⏱ ${duration < 1e3 ? `${duration}ms` : `${(duration / 1e3).toFixed(1)}s`}`,
280
+ value: `⏱ ${interval(duration)}`,
286
281
  tone: "muted"
287
282
  });
288
283
  return {
@@ -292,6 +287,177 @@ function tooltipSections(event, index, cutoff) {
292
287
  sections
293
288
  };
294
289
  }
290
+ const time = (value) => typeof value === "string" ? ms(value) : NaN;
291
+ /** Project retained identities and measured intervals; a message without a duration remains a point. */
292
+ function recordFlowTrace(record, live = false, observedAt = Date.now()) {
293
+ const index = indexRecord(record);
294
+ const first = index.events.length || index.actors.some((node) => Number.isFinite(time(node.start))) ? index.start : observedAt;
295
+ const last = Math.max(first, index.end, live ? observedAt : first);
296
+ const spans = [];
297
+ const actors = new Set(index.actors.map((node) => node.id));
298
+ const runningNodes = new Set(index.actors.filter((node) => live && !node.end && isOpen(node.status ?? "unknown")).map((node) => node.id));
299
+ const offset = (at) => Math.max(0, at - first);
300
+ for (const node of index.actors) {
301
+ if (node.kind === "finding") continue;
302
+ const events = index.byActor.get(node.id) ?? [];
303
+ const start = Number.isFinite(time(node.start)) ? time(node.start) : events[0] ? ms(events[0].at) : NaN;
304
+ if (!Number.isFinite(start)) continue;
305
+ const end = runningNodes.has(node.id) ? last : Number.isFinite(time(node.end)) ? time(node.end) : events.at(-1) ? ms(events.at(-1).at) : start;
306
+ const parent = node.parent ? index.canonical(node.parent) : void 0;
307
+ spans.push({
308
+ id: node.id,
309
+ ...parent && actors.has(parent) ? { parentId: parent } : {},
310
+ kind: "pipeline",
311
+ name: node.label,
312
+ startMs: offset(start),
313
+ endMs: offset(Math.max(start, end)),
314
+ status: runStatus(node.status ?? "unknown"),
315
+ approx: !node.start || !node.end
316
+ });
317
+ if (parent && actors.has(parent)) spans.push({
318
+ id: `${node.id}:handoff`,
319
+ parentId: parent,
320
+ kind: "handoff",
321
+ name: `Handoff to ${node.label}`,
322
+ startMs: offset(start),
323
+ endMs: offset(start),
324
+ approx: true,
325
+ meta: { timing: "Recorded child start; handoff duration was not measured" }
326
+ });
327
+ }
328
+ const promptTokens = [];
329
+ const completionTokens = [];
330
+ let cost = 0;
331
+ let costKnown = false;
332
+ let costMissing = false;
333
+ const explicitTools = /* @__PURE__ */ new Set();
334
+ let toolCalls = 0;
335
+ for (const event of index.events) {
336
+ const detail = event.detail;
337
+ const response = detail.usageScope === void 0 && detail.role === "assistant";
338
+ const counters = [
339
+ detail.usage?.input,
340
+ detail.usage?.cacheRead,
341
+ detail.usage?.cacheWrite,
342
+ detail.usage?.output
343
+ ].map(reportedCount);
344
+ const prompt = sumReportedCounts(counters.slice(0, 3)), completion = counters[3];
345
+ const measured = counters.filter((value) => value !== void 0);
346
+ const facts = measured.length && measured.length < counters.length ? [{
347
+ label: "Tokens",
348
+ value: `≥ ${fmt(measured.reduce((sum, value) => sum + value, 0))} measured; incomplete counters`
349
+ }] : void 0;
350
+ if (detail.usageScope === void 0) {
351
+ if (response || detail.usage) {
352
+ promptTokens.push(prompt);
353
+ completionTokens.push(completion);
354
+ }
355
+ if (detail.usdKnown === false) costMissing = true;
356
+ else if (typeof detail.costListUsd === "number") {
357
+ cost += detail.costListUsd;
358
+ costKnown = true;
359
+ } else if (detail.usage && Object.values(detail.usage).some((value) => typeof value === "number" && value > 0)) costMissing = true;
360
+ }
361
+ const start = ms(event.at);
362
+ const parentId = index.canonical(event.node);
363
+ if (typeof detail.spanId === "string") {
364
+ const kind = event.kind.replace(/^trace-/, "").toLowerCase();
365
+ const end = time(detail.end);
366
+ const state = runStatus(typeof detail.status === "string" ? detail.status : "unknown");
367
+ const running = live && !Number.isFinite(end) && [
368
+ "running",
369
+ "waiting",
370
+ "needs-you"
371
+ ].includes(state);
372
+ const spanKind = ["llm", "model"].includes(kind) ? "model" : kind === "tool" ? "tool" : kind === "waiting" ? "waiting" : kind === "handoff" ? "handoff" : "pipeline";
373
+ const traceId = String(detail.traceId ?? event.node);
374
+ spans.push({
375
+ id: `${traceId}:${detail.spanId}`,
376
+ parentId: typeof detail.parentSpanId === "string" ? `${traceId}:${detail.parentSpanId}` : parentId,
377
+ kind: spanKind,
378
+ name: event.label,
379
+ startMs: offset(start),
380
+ endMs: offset(Number.isFinite(end) ? Math.max(start, end) : running ? last : start),
381
+ status: detail.isError ? "failed" : state,
382
+ meta: { eventId: event.id }
383
+ });
384
+ if (spanKind === "tool") {
385
+ toolCalls++;
386
+ if (typeof detail.toolCallId === "string") explicitTools.add(toolKey(event.node, detail.toolCallId));
387
+ }
388
+ } else if (response) spans.push({
389
+ id: event.id,
390
+ parentId,
391
+ kind: "model",
392
+ name: event.label,
393
+ startMs: offset(start),
394
+ endMs: offset(start),
395
+ approx: true,
396
+ status: runStatus(typeof detail.responseStatus === "string" ? detail.responseStatus : "unknown"),
397
+ promptTokens: prompt,
398
+ completionTokens: completion,
399
+ facts,
400
+ meta: {
401
+ eventId: event.id,
402
+ timing: "Response timestamp; generation duration was not recorded"
403
+ }
404
+ });
405
+ }
406
+ for (const key of /* @__PURE__ */ new Set([...index.calls.keys(), ...index.results.keys()])) {
407
+ if (explicitTools.has(key)) continue;
408
+ const calls = index.calls.get(key) ?? [], results = index.results.get(key) ?? [];
409
+ const result = calls.length === 1 && results.length === 1 && ms(results[0].at) >= ms(calls[0].event.at) ? results[0] : void 0;
410
+ const timing = calls.length > 1 || results.length > 1 ? "Ambiguous retained call/result identity; no duration measured" : calls.length && results.length ? "Recorded result timestamp precedes call; no interval measured" : "Unmatched retained call or result; no interval measured";
411
+ for (const { event, call } of calls) {
412
+ toolCalls++;
413
+ const start = ms(event.at), parentId = index.canonical(event.node);
414
+ spans.push({
415
+ id: `${event.id}:tool:${call.id}`,
416
+ parentId,
417
+ kind: "tool",
418
+ name: call.name,
419
+ startMs: offset(start),
420
+ endMs: offset(result ? ms(result.at) : start),
421
+ approx: !result || event.detail.atBasis === "carried" || result.detail.atBasis === "carried",
422
+ status: result?.detail.isError ? "failed" : result ? "done" : "unknown",
423
+ facts: result ? void 0 : [{
424
+ label: "Timing",
425
+ value: timing
426
+ }],
427
+ meta: {
428
+ eventId: event.id,
429
+ ...!result ? { timing } : {}
430
+ }
431
+ });
432
+ }
433
+ if (!result) for (const event of results) spans.push({
434
+ id: `${event.id}:result`,
435
+ parentId: index.canonical(event.node),
436
+ kind: "tool",
437
+ name: event.label,
438
+ startMs: offset(ms(event.at)),
439
+ endMs: offset(ms(event.at)),
440
+ approx: true,
441
+ status: event.detail.isError ? "failed" : "unknown",
442
+ facts: [{
443
+ label: "Timing",
444
+ value: timing
445
+ }],
446
+ meta: { eventId: event.id }
447
+ });
448
+ }
449
+ return {
450
+ spans,
451
+ totalMs: last - first,
452
+ promptTokens: sumReportedCounts(promptTokens),
453
+ completionTokens: sumReportedCounts(completionTokens),
454
+ toolCalls,
455
+ ...costKnown && !costMissing ? {
456
+ costUsd: cost,
457
+ costBasis: "list"
458
+ } : {}
459
+ };
460
+ }
295
461
  // src/run/record/conversation-rows.ts
296
462
  /**
297
463
  * A key that survives a live record growing. Event ids hash the whole source file, so every append renames every
@@ -516,11 +682,7 @@ function pollSummary(events) {
516
682
  measured++;
517
683
  tokens += parts.reduce((sum, value) => sum + value, 0);
518
684
  }
519
- const count = new Intl.NumberFormat("en-US", {
520
- notation: "compact",
521
- maximumFractionDigits: 1
522
- });
523
- const tokenText = !measured ? "tokens unknown" : measured < events.length ? `~${count.format(tokens)} tokens (${measured} of ${events.length} measured)` : `~${count.format(tokens)} tokens`;
685
+ const tokenText = !measured ? "tokens unknown" : measured < events.length ? `~${formatCompact(tokens)} tokens (${measured} of ${events.length} measured)` : `~${formatCompact(tokens)} tokens`;
524
686
  return {
525
687
  polls: events.length,
526
688
  spanMs: span,
@@ -551,11 +713,6 @@ function promptText(text) {
551
713
  // src/run/record/ConversationRowView.tsx
552
714
  /** A run-lifecycle event (start, stop, restart), drawn as a thin marker row. */
553
715
  const isLifecycle = (event) => event.category === "lifecycle" || event.detail.lifecycle !== void 0;
554
- const tokenLabel = (value) => new Intl.NumberFormat("en-US", {
555
- notation: "compact",
556
- maximumFractionDigits: 1
557
- }).format(value);
558
- const spanLabel = (value) => value < 1e3 ? `${Math.round(value)} ms` : formatDuration(value);
559
716
  function ToolStep({ item, previous }) {
560
717
  const part = item.part, cost = part.state.metadata?.costUsd, time = part.state.time, category = getToolCategory(part.tool);
561
718
  const tone = part.state.status === "error" ? "var(--surface-danger-text)" : [
@@ -588,12 +745,12 @@ function ToolStep({ item, previous }) {
588
745
  /* @__PURE__ */ jsx("dt", { children: "Duration" }),
589
746
  /* @__PURE__ */ jsx("dd", {
590
747
  className: "m-0 text-right",
591
- children: time?.end != null && time.start != null ? spanLabel(time.end - time.start) : "Not recorded"
748
+ children: time?.end != null && time.start != null ? formatSpanDuration(time.end - time.start) : "Not recorded"
592
749
  }),
593
750
  /* @__PURE__ */ jsx("dt", { children: "Cost" }),
594
751
  /* @__PURE__ */ jsx("dd", {
595
752
  className: "m-0 text-right",
596
- children: typeof cost === "number" ? `$${cost.toFixed(4)}` : "Not recorded"
753
+ children: typeof cost === "number" ? formatCost(cost) : "Not recorded"
597
754
  })
598
755
  ]
599
756
  }),
@@ -722,7 +879,7 @@ function toolItems(id, call, result, state, durationMs, live, opener) {
722
879
  }
723
880
  const toolNote = (part) => {
724
881
  const note = part.state.metadata?.note, time = part.state.time;
725
- const duration = time?.end != null && time.start != null ? spanLabel(time.end - time.start) : "Not recorded";
882
+ const duration = time?.end != null && time.start != null ? formatSpanDuration(time.end - time.start) : "Not recorded";
726
883
  const result = typeof note === "string" ? note : part.state.status === "error" ? "Failed" : part.state.status === "running" ? "Running" : "Returned";
727
884
  return /* @__PURE__ */ jsxs("span", {
728
885
  "data-record-tool-summary": "",
@@ -870,7 +1027,7 @@ function rowItems(row, context) {
870
1027
  if (withheld.length) {
871
1028
  const output = outputTokens(event.detail);
872
1029
  const span = thinkingSpan.get(event.id);
873
- const label = output !== null ? `${span !== void 0 && span >= 1e3 ? `for ${spanLabel(span)} · ` : ""}${tokenLabel(output)} output tokens · ${withheld.join(" · ")}` : withheld.join(" · ");
1030
+ const label = output !== null ? `${span !== void 0 && span >= 1e3 ? `for ${formatSpanDuration(span)} · ` : ""}${formatCompact(output)} output tokens · ${withheld.join(" · ")}` : withheld.join(" · ");
874
1031
  items.push({
875
1032
  id: `${event.id}:thinking`,
876
1033
  kind: "custom",
@@ -1249,50 +1406,6 @@ function Conversation({ index, actor, cutoff, query, category, selected, openSou
1249
1406
  })]
1250
1407
  }) });
1251
1408
  }
1252
- // src/run/record/prompts.ts
1253
- /** The conversation needs an opening prompt even when the provider did not retain its first user turn. */
1254
- function withPrompts(record) {
1255
- const events = record.events.map((event) => ({
1256
- ...event,
1257
- detail: { ...event.detail }
1258
- }));
1259
- const owners = new Map(record.nodes.map((node) => [node.id, node.agentId ?? node.id]));
1260
- for (const node of record.nodes.filter((item) => item.kind === "agent")) {
1261
- const own = events.filter((event) => (owners.get(event.node) ?? event.node) === node.id);
1262
- const firstReply = own.find((event) => event.detail.role === "assistant")?.at;
1263
- const userTurns = own.filter((event) => event.detail.role === "user" && typeof event.detail.publicText === "string" && !event.detail.publicText.trimStart().startsWith("<task-notification>"));
1264
- const initial = userTurns.find((event) => event.detail.promptKind !== "steering" && (!firstReply || event.at <= firstReply));
1265
- if (initial) initial.detail.promptKind = "initial";
1266
- else if (node.assignment?.trim()) {
1267
- const at = node.start ?? own[0]?.at;
1268
- if (at) events.push({
1269
- id: `prompt:${node.id}`,
1270
- node: node.id,
1271
- at,
1272
- kind: "message",
1273
- category: "other",
1274
- label: "Prompt",
1275
- source: null,
1276
- detail: {
1277
- role: "user",
1278
- publicText: node.assignment,
1279
- promptKind: "initial",
1280
- promptReconstructed: true,
1281
- promptSource: "node.assignment"
1282
- }
1283
- });
1284
- }
1285
- for (const event of userTurns) {
1286
- if (event === initial || event.detail.promptKind === "initial") continue;
1287
- event.detail.promptKind ??= "steering";
1288
- }
1289
- }
1290
- events.sort((a, b) => a.at.localeCompare(b.at) || Number(b.detail.promptKind === "initial") - Number(a.detail.promptKind === "initial"));
1291
- return {
1292
- ...record,
1293
- events
1294
- };
1295
- }
1296
1409
  // src/run/record/AgentTree.tsx
1297
1410
  /** Retained identity and recorded time define the hierarchy; labels never join sessions or invent parents. */
1298
1411
  function AgentTree({ index, actor, cutoff, select }) {
@@ -1324,9 +1437,9 @@ function AgentTree({ index, actor, cutoff, select }) {
1324
1437
  children: entries.map(({ node, children }) => {
1325
1438
  const firstEvent = index.byActor.get(node.id)?.[0];
1326
1439
  const begin = node.start ? ms(node.start) : firstEvent ? ms(firstEvent.at) : void 0;
1327
- const end = typeof node.end === "string" ? node.end : void 0;
1328
- const future = begin !== void 0 && begin > cutoff, ended = end !== void 0 && ms(end) <= cutoff;
1329
- const state = future ? "Not yet started" : ended ? node.status === "done" ? "Finished" : node.status === "down" ? "Stopped" : node.status ?? "Ended" : end ? "Not yet settled" : "No terminal record";
1440
+ const future = begin !== void 0 && begin > cutoff, pending = typeof node.end === "string" && ms(node.end) > cutoff;
1441
+ const status = runStatus(future || pending ? "unknown" : node.status ?? "unknown");
1442
+ const state = future ? "Not yet started" : pending ? "Not yet settled" : statusMeta(status).label;
1330
1443
  return /* @__PURE__ */ jsxs("li", {
1331
1444
  className: "min-w-0",
1332
1445
  children: [/* @__PURE__ */ jsxs("button", {
@@ -1339,7 +1452,7 @@ function AgentTree({ index, actor, cutoff, select }) {
1339
1452
  title: node.label + (node.assignment ? " · " + node.assignment : "") + " · " + state,
1340
1453
  onClick: () => select(node.id),
1341
1454
  children: [/* @__PURE__ */ jsx(RunStatusIcon, {
1342
- status: runStatus(future ? "unknown" : node.status === "down" || node.status === "no-winner" ? "stopped" : node.status ?? "unknown"),
1455
+ status,
1343
1456
  className: "mt-0.5 size-3.5 shrink-0",
1344
1457
  "aria-hidden": true
1345
1458
  }), /* @__PURE__ */ jsxs("span", {
@@ -1371,4 +1484,4 @@ function AgentTree({ index, actor, cutoff, select }) {
1371
1484
  })
1372
1485
  });
1373
1486
  }
1374
- export { model_exports as a, indexRecord as i, withPrompts as n, Conversation as r, AgentTree as t };
1487
+ export { fmt as a, modelIdentity as c, recordFlowTrace as d, roleOf as f, utcTime as h, eventSource as i, model_exports as l, toolKey as m, Conversation as n, indexRecord as o, textOf as p, eventMatches as r, interval as s, AgentTree as t, ms as u };