@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 +17 -0
- package/dist/{record-CnxPRyxC.js → AgentTree-KO1TJXBq.js} +194 -81
- package/dist/{WorkflowGraph-CADReIDl.js → WorkflowGraph-B8cRtVaw.js} +2 -2
- package/dist/globals.css +5910 -240
- package/dist/{index-DxMX-DlX.d.ts → index-BoKSR0s9.d.ts} +1 -1
- package/dist/{index-1qlc9-S2.d.ts → index-gXcuwPOX.d.ts} +5 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/record-BGcR48YY.js +47 -0
- package/dist/record.d.ts +66 -0
- package/dist/record.js +2329 -0
- package/dist/report-viewer.js +554 -0
- package/dist/run/record.d.ts +2 -2
- package/dist/run/record.js +3 -2
- package/dist/run.d.ts +3 -3
- package/dist/run.js +3 -2
- package/dist/runtime-viewer/{AgentGraphCanvas-B74GN1o6-DC3EH_ss.js → AgentGraphCanvas-DDvaU9Rg-DlNZxJ20.js} +187 -168
- package/dist/runtime-viewer/{latest-events-iiWwpIck-BTK0vSlR.js → latest-events-CabhXynX-Dp_vs0Px.js} +705 -691
- package/dist/runtime-viewer/sandbox-ui-CUj4Q1P_.css +2 -0
- package/dist/styles.css +5911 -241
- package/dist/tailwind.css +5677 -5
- package/dist/tokens.css +7 -1
- package/dist/workflows.js +1 -1
- package/dist/workspace-viewer.js +9096 -9152
- package/package.json +26 -12
- package/dist/runtime-viewer/sandbox-ui-z5RGk327.css +0 -2
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 {
|
|
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
|
|
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:
|
|
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: `⏱ ${
|
|
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
|
|
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 ?
|
|
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" ?
|
|
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 ?
|
|
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 ${
|
|
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
|
|
1328
|
-
const
|
|
1329
|
-
const state = future ? "Not yet started" :
|
|
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
|
|
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 {
|
|
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,
|