@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/README.md CHANGED
@@ -531,3 +531,20 @@ Retheming is absolutely supported, but the documentation was thinner than it sho
531
531
  Apache-2.0
532
532
 
533
533
  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.
534
+
535
+ ## Retained records and reports
536
+
537
+ `@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.
538
+
539
+ ```tsx
540
+ import { parseRecord } from '@drewstone/agent-record/record'
541
+ import { AgentRecord } from '@tangle-network/sandbox-ui/record'
542
+ import '@tangle-network/sandbox-ui/styles'
543
+
544
+ const record = parseRecord(input)
545
+ <AgentRecord records={[record]} />
546
+ ```
547
+
548
+ 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.
549
+
550
+ 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.
@@ -5,9 +5,10 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from "@tangle-network/u
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";
8
+ import { getToolCategory, getToolDisplayMetadata } from "@tangle-network/ui/utils";
9
9
  import { AgentTimelineColumn, AgentTimelineStep, SpeakerHeader, agentTimelineStepKind } from "@tangle-network/ui/chat";
10
- import { ExpandedToolDetail, InlineThinkingItem, RunRowShell, RunStatusIcon, runStatus } from "@tangle-network/ui/run";
10
+ import { ExpandedToolDetail, InlineThinkingItem, RunRowShell, RunStatusIcon, runStatus, statusMeta } from "@tangle-network/ui/run";
11
+ import { formatCompact, formatCompact as fmt, formatCost, formatSpanDuration, formatSpanDuration as interval, formatUtcTime as utcTime, reportedCount, sumReportedCounts } from "@tangle-network/charts";
11
12
  // src/run/record/model.ts
12
13
  var model_exports = /* @__PURE__ */ __exportAll({
13
14
  agentState: () => agentState,
@@ -24,6 +25,7 @@ var model_exports = /* @__PURE__ */ __exportAll({
24
25
  modelIdentity: () => modelIdentity,
25
26
  ms: () => ms,
26
27
  preview: () => preview,
28
+ recordFlowTrace: () => recordFlowTrace,
27
29
  roleOf: () => roleOf,
28
30
  textOf: () => textOf,
29
31
  toolKey: () => toolKey,
@@ -31,13 +33,6 @@ var model_exports = /* @__PURE__ */ __exportAll({
31
33
  utcTime: () => utcTime
32
34
  });
33
35
  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
36
  const compact = (value) => value.replace(/\s+/g, " ").trim();
42
37
  /**
43
38
  * Recorded text names the host folders an agent ran in (`/home/<user>/.cache/disco/host/<run>/sessions/<session>/inputs/x.md`).
@@ -72,11 +67,6 @@ function withoutHostPaths(event) {
72
67
  }
73
68
  };
74
69
  }
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
70
  const roleOf = (node) => node?.role ?? (node?.kind === "session" ? "Session" : node?.kind === "finding" ? "Finding" : "Agent");
81
71
  const textOf = (event) => event.detail.publicText ?? event.detail.recordedClaim ?? "";
82
72
  const toolKey = (node, id) => JSON.stringify([node, id]);
@@ -157,8 +147,9 @@ function indexRecord(record) {
157
147
  results.set(key, matching);
158
148
  }
159
149
  }
160
- const times = events.map((event) => ms(event.at));
161
- const start = times[0] ?? 0;
150
+ const times = [...events.map((event) => ms(event.at)), ...actors.flatMap((node) => node.start ? [ms(node.start)] : [])].filter(Number.isFinite);
151
+ const start = times.length ? times.reduce((first, at) => Math.min(first, at)) : 0;
152
+ const observedEnd = events.reduce((latest, event) => Math.max(latest, event.end ? ms(event.end) : ms(event.at)), start);
162
153
  return {
163
154
  nodes,
164
155
  canonical,
@@ -170,7 +161,7 @@ function indexRecord(record) {
170
161
  calls,
171
162
  results,
172
163
  start,
173
- end: events.reduce((latest, event) => Math.max(latest, event.end ? ms(event.end) : ms(event.at)), times.at(-1) ?? start)
164
+ end: actors.reduce((latest, node) => typeof node.end === "string" && Number.isFinite(ms(node.end)) ? Math.max(latest, ms(node.end)) : latest, observedEnd)
174
165
  };
175
166
  }
176
167
  /** Whether any event of this agent recorded token usage or a list price: a status alone measures nothing. */
@@ -282,7 +273,7 @@ function tooltipSections(event, index, cutoff) {
282
273
  const duration = event.detail.durationMs;
283
274
  if (typeof duration === "number") sections.push({
284
275
  kind: "status",
285
- value: `⏱ ${duration < 1e3 ? `${duration}ms` : `${(duration / 1e3).toFixed(1)}s`}`,
276
+ value: `⏱ ${interval(duration)}`,
286
277
  tone: "muted"
287
278
  });
288
279
  return {
@@ -292,6 +283,181 @@ function tooltipSections(event, index, cutoff) {
292
283
  sections
293
284
  };
294
285
  }
286
+ const time = (value) => typeof value === "string" ? ms(value) : NaN;
287
+ /** Project retained identities and measured intervals; a message without a duration remains a point. */
288
+ function recordFlowTrace(record, live = false, observedAt = Date.now()) {
289
+ const index = indexRecord(record);
290
+ const first = index.events.length || index.actors.some((node) => Number.isFinite(time(node.start))) ? index.start : observedAt;
291
+ const last = Math.max(first, index.end, live ? observedAt : first);
292
+ const spans = [];
293
+ const actors = new Set(index.actors.map((node) => node.id));
294
+ const runningNodes = new Set(index.actors.filter((node) => live && !node.end && [
295
+ "running",
296
+ "waiting",
297
+ "needs-you"
298
+ ].includes(runStatus(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 };
@@ -3,7 +3,7 @@ import { n as classifyOutput, t as NodeOutputBody } from "./node-output-DlySTwxr
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { Expand, Maximize2, Minimize2, Plus, Shrink, X } from "lucide-react";
6
- import { SELECTION_OUTLINE, loadElk, motionAllowed, useColorMode } from "@tangle-network/charts/react/agent-graph/presentation";
6
+ import { SELECTION_OUTLINE, loadElk, motionAllowed, useColorMode as useColorMode$1 } from "@tangle-network/charts/react/agent-graph/presentation";
7
7
  import { Background, BaseEdge, Controls, EdgeLabelRenderer, Handle, MarkerType, MiniMap, Panel, Position, ReactFlow, ViewportPortal, getSmoothStepPath, getViewportForBounds, useEdgesState, useNodesState, useStore } from "@xyflow/react";
8
8
  import "@xyflow/react/dist/style.css";
9
9
  // src/workflows/flow-graph.ts
@@ -1967,7 +1967,7 @@ function WorkflowGraph({ yaml, variant = "full", direction: directionProp, defau
1967
1967
  ]), direction, compact, canvasAspect, !editable);
1968
1968
  const shownCompact = structural.compact ?? compact;
1969
1969
  const shownDirection = structural.direction ?? direction;
1970
- const colorMode = useColorMode(wrapperRef, !structural.error && structural.nodes.length > 0);
1970
+ const colorMode = useColorMode$1(wrapperRef, !structural.error && structural.nodes.length > 0);
1971
1971
  const nodeBoxes = useMemo(() => structural.nodes.map((node) => ({
1972
1972
  x: node.position.x,
1973
1973
  y: node.position.y,