bosun 0.41.2 → 0.41.4
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/.env.example +1 -1
- package/agent/agent-pool.mjs +9 -2
- package/agent/agent-prompt-catalog.mjs +971 -0
- package/agent/agent-prompts.mjs +2 -970
- package/agent/agent-supervisor.mjs +119 -6
- package/agent/autofix-git.mjs +33 -0
- package/agent/autofix-prompts.mjs +151 -0
- package/agent/autofix.mjs +11 -175
- package/agent/bosun-skills.mjs +3 -2
- package/bosun.config.example.json +17 -0
- package/bosun.schema.json +87 -188
- package/cli.mjs +34 -1
- package/config/config-doctor.mjs +5 -250
- package/config/config-file-names.mjs +5 -0
- package/config/config.mjs +89 -493
- package/config/executor-config.mjs +493 -0
- package/config/repo-root.mjs +1 -2
- package/config/workspace-health.mjs +242 -0
- package/git/git-safety.mjs +15 -0
- package/github/github-oauth-portal.mjs +46 -0
- package/infra/library-manager-utils.mjs +22 -0
- package/infra/library-manager-well-known-sources.mjs +578 -0
- package/infra/library-manager.mjs +512 -1030
- package/infra/monitor.mjs +35 -9
- package/infra/session-tracker.mjs +10 -7
- package/kanban/kanban-adapter.mjs +17 -1
- package/lib/codebase-audit-manifests.mjs +117 -0
- package/lib/codebase-audit.mjs +18 -115
- package/package.json +18 -3
- package/server/setup-web-server.mjs +58 -5
- package/server/ui-server.mjs +1394 -79
- package/shell/codex-config-file.mjs +178 -0
- package/shell/codex-config.mjs +538 -575
- package/task/task-cli.mjs +54 -3
- package/task/task-executor.mjs +143 -13
- package/task/task-store.mjs +409 -1
- package/telegram/telegram-bot.mjs +127 -0
- package/tools/apply-pr-suggestions.mjs +401 -0
- package/tools/syntax-check.mjs +28 -9
- package/ui/app.js +3 -14
- package/ui/components/kanban-board.js +227 -4
- package/ui/components/session-list.js +85 -5
- package/ui/demo-defaults.js +338 -84
- package/ui/demo.html +155 -0
- package/ui/modules/session-api.js +96 -0
- package/ui/modules/settings-schema.js +1 -2
- package/ui/modules/state.js +43 -3
- package/ui/setup.html +4 -5
- package/ui/styles/components.css +58 -4
- package/ui/tabs/agents.js +12 -15
- package/ui/tabs/control.js +1 -0
- package/ui/tabs/library.js +484 -22
- package/ui/tabs/manual-flows.js +105 -29
- package/ui/tabs/tasks.js +848 -141
- package/ui/tabs/telemetry.js +129 -11
- package/ui/tabs/workflow-canvas-utils.mjs +130 -0
- package/ui/tabs/workflows.js +293 -23
- package/voice/voice-tool-definitions.mjs +757 -0
- package/voice/voice-tools.mjs +34 -778
- package/workflow/manual-flow-audit.mjs +165 -0
- package/workflow/manual-flows.mjs +164 -259
- package/workflow/workflow-engine.mjs +147 -58
- package/workflow/workflow-nodes/definitions.mjs +1207 -0
- package/workflow/workflow-nodes/transforms.mjs +612 -0
- package/workflow/workflow-nodes.mjs +358 -63
- package/workflow/workflow-templates.mjs +313 -191
- package/workflow-templates/_helpers.mjs +154 -0
- package/workflow-templates/agents.mjs +61 -4
- package/workflow-templates/code-quality.mjs +7 -7
- package/workflow-templates/github.mjs +20 -10
- package/workflow-templates/task-batch.mjs +44 -11
- package/workflow-templates/task-lifecycle.mjs +31 -6
- package/workspace/worktree-manager.mjs +277 -3
package/ui/tabs/telemetry.js
CHANGED
|
@@ -260,6 +260,15 @@ function formatBytes(n) {
|
|
|
260
260
|
return `${n}`;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
function formatUsd(value) {
|
|
264
|
+
const amount = Number(value);
|
|
265
|
+
if (!Number.isFinite(amount)) return "–";
|
|
266
|
+
if (amount === 0) return "$0.00";
|
|
267
|
+
if (Math.abs(amount) < 0.01) return `$${amount.toFixed(4)}`;
|
|
268
|
+
if (Math.abs(amount) < 1) return `$${amount.toFixed(3)}`;
|
|
269
|
+
return `$${amount.toFixed(2)}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
263
272
|
function formatShreddingLabel(value) {
|
|
264
273
|
return String(value || "unknown")
|
|
265
274
|
.replace(/[_-]+/g, " ")
|
|
@@ -318,10 +327,16 @@ function ShreddingPanel({ period }) {
|
|
|
318
327
|
const {
|
|
319
328
|
totalEvents = 0,
|
|
320
329
|
totalOriginalChars = 0,
|
|
330
|
+
totalCompressedChars = 0,
|
|
321
331
|
totalSavedChars = 0,
|
|
322
332
|
avgSavedPct = 0,
|
|
323
333
|
sortedDates = [],
|
|
334
|
+
dailyOriginal = {},
|
|
335
|
+
dailyCompressed = {},
|
|
324
336
|
dailySaved = {},
|
|
337
|
+
dailySavedTokensEstimated = {},
|
|
338
|
+
dailyCostSavedUsd = {},
|
|
339
|
+
dailyReductionPct = {},
|
|
325
340
|
dailyCounts = {},
|
|
326
341
|
topAgents = [],
|
|
327
342
|
stageCounts = {},
|
|
@@ -330,16 +345,38 @@ function ShreddingPanel({ period }) {
|
|
|
330
345
|
liveCompaction = {},
|
|
331
346
|
recentEvents = [],
|
|
332
347
|
diagnostics = {},
|
|
348
|
+
totals = {},
|
|
349
|
+
estimation = {},
|
|
333
350
|
} = data;
|
|
334
351
|
|
|
335
|
-
const
|
|
352
|
+
const volumeSeriesMap = {
|
|
353
|
+
original: sortedDates.map((d) => dailyOriginal[d] || 0),
|
|
354
|
+
compressed: sortedDates.map((d) => dailyCompressed[d] || 0),
|
|
355
|
+
saved: sortedDates.map((d) => dailySaved[d] || 0),
|
|
356
|
+
};
|
|
357
|
+
const reductionSeriesMap = {
|
|
358
|
+
"reduction %": sortedDates.map((d) => dailyReductionPct[d] || 0),
|
|
359
|
+
};
|
|
360
|
+
const tokenSeriesMap = {
|
|
361
|
+
"tokens saved": sortedDates.map((d) => dailySavedTokensEstimated[d] || 0),
|
|
362
|
+
};
|
|
363
|
+
const costSeriesMap = {
|
|
364
|
+
"cost avoided": sortedDates.map((d) => dailyCostSavedUsd[d] || 0),
|
|
365
|
+
};
|
|
336
366
|
const sparkCounts = sortedDates.map((d) => dailyCounts[d] || 0);
|
|
337
367
|
const stageItems = Object.entries(stageCounts)
|
|
338
368
|
.sort((a, b) => b[1] - a[1])
|
|
339
369
|
.map(([name, count]) => ({ name: formatShreddingLabel(name), count }));
|
|
340
370
|
const liveEvents = liveCompaction?.totalEvents || stageCounts.live_tool_compaction || 0;
|
|
341
371
|
const liveSavedChars = liveCompaction?.totalSavedChars || 0;
|
|
372
|
+
const liveSavedTokensEstimated = liveCompaction?.savedTokensEstimated || 0;
|
|
342
373
|
const liveAvgSavedPct = liveCompaction?.avgSavedPct || 0;
|
|
374
|
+
const totalSavedTokensEstimated = totals?.savedTokensEstimated || 0;
|
|
375
|
+
const totalEstimatedCostSavedUsd = totals?.estimatedCostSavedUsd ?? null;
|
|
376
|
+
const hasCostEstimate = Number.isFinite(Number(totalEstimatedCostSavedUsd))
|
|
377
|
+
&& Number(totalEstimatedCostSavedUsd) > 0;
|
|
378
|
+
const hasCostTrend = sortedDates.some((day) => Number(dailyCostSavedUsd?.[day] || 0) > 0);
|
|
379
|
+
const observedCostRate = estimation?.blendedCostPerMillionTokensUsd ?? null;
|
|
343
380
|
|
|
344
381
|
return html`
|
|
345
382
|
<${Paper} elevation=${1} sx=${{ p: 2, mb: 2 }}>
|
|
@@ -355,11 +392,14 @@ function ShreddingPanel({ period }) {
|
|
|
355
392
|
|
|
356
393
|
<${Stack} direction=${{ xs: "column", sm: "row" }} spacing=${1.5} sx=${{ mb: 2, flexWrap: "wrap" }}>
|
|
357
394
|
<${AnalyticsStat} icon="✂" label="Events" value=${formatCount(totalEvents)} />
|
|
395
|
+
<${AnalyticsStat} icon="📦" label="Original Chars" value=${formatBytes(totalOriginalChars)} />
|
|
396
|
+
<${AnalyticsStat} icon="🧱" label="Compressed Chars" value=${formatBytes(totalCompressedChars)} />
|
|
358
397
|
<${AnalyticsStat} icon="📉" label="Chars Saved" value=${formatBytes(totalSavedChars)} />
|
|
359
398
|
<${AnalyticsStat} icon="%" label="Avg Reduction" value=${avgSavedPct > 0 ? `${avgSavedPct}%` : "–"} />
|
|
360
|
-
<${AnalyticsStat} icon="
|
|
399
|
+
<${AnalyticsStat} icon="🧮" label="Est. Tokens Saved" value=${formatCount(totalSavedTokensEstimated)} />
|
|
400
|
+
<${AnalyticsStat} icon="💵" label="Est. Cost Avoided" value=${hasCostEstimate ? formatUsd(totalEstimatedCostSavedUsd) : "Unavailable"} />
|
|
361
401
|
<${AnalyticsStat} icon="⚡" label="Live Events" value=${formatCount(liveEvents)} />
|
|
362
|
-
<${AnalyticsStat} icon="🧠" label="Live
|
|
402
|
+
<${AnalyticsStat} icon="🧠" label="Live Saved Tokens" value=${formatCount(liveSavedTokensEstimated)} />
|
|
363
403
|
<//>
|
|
364
404
|
|
|
365
405
|
${(diagnostics?.excludedSynthetic || diagnostics?.excludedNoop || diagnostics?.unknownAttribution)
|
|
@@ -373,14 +413,24 @@ function ShreddingPanel({ period }) {
|
|
|
373
413
|
`
|
|
374
414
|
: null}
|
|
375
415
|
|
|
416
|
+
<${Alert} severity=${hasCostEstimate ? "success" : "info"} sx=${{ mb: 2 }}>
|
|
417
|
+
Token savings are estimated from characters using ${estimation?.charsPerToken || 4} chars per token.
|
|
418
|
+
${hasCostEstimate
|
|
419
|
+
? ` Cost avoided uses the observed blended session rate of ${formatUsd((observedCostRate || 0) / 1_000_000)} per token (${formatUsd(observedCostRate)} per million) across ${formatCount(estimation?.pricedSessions || 0)} priced session${(estimation?.pricedSessions || 0) === 1 ? "" : "s"}.`
|
|
420
|
+
: " Cost avoided is unavailable because recent completed sessions did not record usable token-and-cost pairs."}
|
|
421
|
+
<//>
|
|
422
|
+
|
|
376
423
|
<${Stack} direction=${{ xs: "column", md: "row" }} spacing=${2} sx=${{ mb: 2 }}>
|
|
377
424
|
<${Paper} variant="outlined" sx=${{ p: 1.5, flex: 1 }}>
|
|
378
|
-
<${Typography} variant="subtitle2" gutterBottom>
|
|
379
|
-
|
|
425
|
+
<${Typography} variant="subtitle2" gutterBottom>Context Volume per Day<//>
|
|
426
|
+
<${Typography} variant="caption" color="text.secondary">
|
|
427
|
+
Original context versus compressed output and net savings.
|
|
428
|
+
<//>
|
|
429
|
+
${sortedDates.length > 1 ? html`
|
|
380
430
|
<${Box} sx=${{ overflow: "hidden" }}>
|
|
381
431
|
<${TrendLines}
|
|
382
432
|
dates=${sortedDates}
|
|
383
|
-
seriesMap=${
|
|
433
|
+
seriesMap=${volumeSeriesMap}
|
|
384
434
|
palette=${SHRED_PALETTE}
|
|
385
435
|
/>
|
|
386
436
|
<//>
|
|
@@ -388,13 +438,35 @@ function ShreddingPanel({ period }) {
|
|
|
388
438
|
<//>
|
|
389
439
|
|
|
390
440
|
<${Paper} variant="outlined" sx=${{ p: 1.5, flex: 1 }}>
|
|
391
|
-
<${Typography} variant="subtitle2" gutterBottom>
|
|
392
|
-
<${
|
|
441
|
+
<${Typography} variant="subtitle2" gutterBottom>Reduction Efficiency<//>
|
|
442
|
+
<${Typography} variant="caption" color="text.secondary">
|
|
443
|
+
Daily reduction rate with ${sparkCounts.length ? sparkCounts.reduce((sum, value) => sum + value, 0) : 0} tracked events in this window.
|
|
444
|
+
<//>
|
|
445
|
+
${sortedDates.length > 1 ? html`
|
|
446
|
+
<${Box} sx=${{ overflow: "hidden" }}>
|
|
447
|
+
<${TrendLines}
|
|
448
|
+
dates=${sortedDates}
|
|
449
|
+
seriesMap=${reductionSeriesMap}
|
|
450
|
+
palette=${SHRED_PALETTE}
|
|
451
|
+
/>
|
|
452
|
+
<//>
|
|
453
|
+
` : html`<${EmptyState} title="Not enough data" description="Need ≥2 days of events." />`}
|
|
393
454
|
<//>
|
|
394
455
|
|
|
395
456
|
<${Paper} variant="outlined" sx=${{ p: 1.5, flex: 1 }}>
|
|
396
|
-
<${Typography} variant="subtitle2" gutterBottom>
|
|
397
|
-
<${
|
|
457
|
+
<${Typography} variant="subtitle2" gutterBottom>Estimated Input Tokens Saved<//>
|
|
458
|
+
<${Typography} variant="caption" color="text.secondary">
|
|
459
|
+
Estimated prompt-token savings from context compaction.
|
|
460
|
+
<//>
|
|
461
|
+
${sortedDates.length > 1 ? html`
|
|
462
|
+
<${Box} sx=${{ overflow: "hidden" }}>
|
|
463
|
+
<${TrendLines}
|
|
464
|
+
dates=${sortedDates}
|
|
465
|
+
seriesMap=${tokenSeriesMap}
|
|
466
|
+
palette=${SHRED_PALETTE}
|
|
467
|
+
/>
|
|
468
|
+
<//>
|
|
469
|
+
` : html`<${EmptyState} title="Not enough data" description="Need ≥2 days of events." />`}
|
|
398
470
|
<//>
|
|
399
471
|
<//>
|
|
400
472
|
|
|
@@ -416,6 +488,29 @@ function ShreddingPanel({ period }) {
|
|
|
416
488
|
<//>
|
|
417
489
|
` : null}
|
|
418
490
|
|
|
491
|
+
<${Stack} direction=${{ xs: "column", md: "row" }} spacing=${2} sx=${{ mb: 2 }}>
|
|
492
|
+
<${Paper} variant="outlined" sx=${{ p: 1.5, flex: 1 }}>
|
|
493
|
+
<${Typography} variant="subtitle2" gutterBottom>Estimated Cost Avoided per Day<//>
|
|
494
|
+
${hasCostTrend ? html`
|
|
495
|
+
<${Box} sx=${{ overflow: "hidden" }}>
|
|
496
|
+
<${TrendLines}
|
|
497
|
+
dates=${sortedDates}
|
|
498
|
+
seriesMap=${costSeriesMap}
|
|
499
|
+
palette=${SHRED_PALETTE}
|
|
500
|
+
/>
|
|
501
|
+
<//>
|
|
502
|
+
` : html`<${EmptyState} title="No cost estimate yet" description="Need recent session pricing data to project API cost savings." />`}
|
|
503
|
+
<//>
|
|
504
|
+
<${Paper} variant="outlined" sx=${{ p: 1.5, flex: 1 }}>
|
|
505
|
+
<${Typography} variant="subtitle2" gutterBottom>By Agent Type<//>
|
|
506
|
+
<${TopBarChart} items=${topAgents} palette=${SHRED_PALETTE} title="By Agent" />
|
|
507
|
+
<//>
|
|
508
|
+
<${Paper} variant="outlined" sx=${{ p: 1.5, flex: 1 }}>
|
|
509
|
+
<${Typography} variant="subtitle2" gutterBottom>By Stage<//>
|
|
510
|
+
<${TopBarChart} items=${stageItems} palette=${SHRED_PALETTE} title="By Stage" />
|
|
511
|
+
<//>
|
|
512
|
+
<//>
|
|
513
|
+
|
|
419
514
|
${(liveEvents > 0 || topCompactionFamilies.length > 0 || topCommandFamilies.length > 0) ? html`
|
|
420
515
|
<${Stack} direction=${{ xs: "column", md: "row" }} spacing=${2} sx=${{ mb: 2 }}>
|
|
421
516
|
<${Paper} variant="outlined" sx=${{ p: 1.5, flex: 1 }}>
|
|
@@ -433,7 +528,7 @@ function ShreddingPanel({ period }) {
|
|
|
433
528
|
Saved ${formatBytes(liveSavedChars)} across ${formatCount(liveEvents)} live-compacted outputs.
|
|
434
529
|
<//>
|
|
435
530
|
<${Typography} variant="caption" color="text.secondary">
|
|
436
|
-
|
|
531
|
+
Estimated ${formatCount(liveSavedTokensEstimated)} input tokens avoided during live compaction.
|
|
437
532
|
<//>
|
|
438
533
|
<${Chip}
|
|
439
534
|
label=${liveAvgSavedPct > 0 ? `${liveAvgSavedPct}% average live reduction` : "Live reductions pending"}
|
|
@@ -456,8 +551,11 @@ function ShreddingPanel({ period }) {
|
|
|
456
551
|
<${TableCell}>Stage<//>
|
|
457
552
|
<${TableCell}>Family<//>
|
|
458
553
|
<${TableCell} align="right">Original<//>
|
|
554
|
+
<${TableCell} align="right">Compressed<//>
|
|
459
555
|
<${TableCell} align="right">Saved<//>
|
|
460
556
|
<${TableCell} align="right">Reduction<//>
|
|
557
|
+
<${TableCell} align="right">Est. Tokens<//>
|
|
558
|
+
<${TableCell} align="right">Est. Cost<//>
|
|
461
559
|
<${TableCell}>Agent<//>
|
|
462
560
|
</${TableRow}>
|
|
463
561
|
<//>
|
|
@@ -480,6 +578,9 @@ function ShreddingPanel({ period }) {
|
|
|
480
578
|
<${TableCell} align="right">
|
|
481
579
|
<${Typography} variant="caption">${formatBytes(ev.originalChars)}<//>
|
|
482
580
|
<//>
|
|
581
|
+
<${TableCell} align="right">
|
|
582
|
+
<${Typography} variant="caption">${formatBytes(ev.compressedChars)}<//>
|
|
583
|
+
<//>
|
|
483
584
|
<${TableCell} align="right">
|
|
484
585
|
<${Typography} variant="caption" color="success.main">
|
|
485
586
|
${ev.savedChars > 0 ? `-${formatBytes(ev.savedChars)}` : "0"}
|
|
@@ -493,6 +594,14 @@ function ShreddingPanel({ period }) {
|
|
|
493
594
|
variant="outlined"
|
|
494
595
|
/>
|
|
495
596
|
<//>
|
|
597
|
+
<${TableCell} align="right">
|
|
598
|
+
<${Typography} variant="caption">${formatCount(ev.estimatedSavedTokens || 0)}<//>
|
|
599
|
+
<//>
|
|
600
|
+
<${TableCell} align="right">
|
|
601
|
+
<${Typography} variant="caption">
|
|
602
|
+
${Number.isFinite(Number(ev.estimatedCostSavedUsd)) ? formatUsd(ev.estimatedCostSavedUsd) : "–"}
|
|
603
|
+
<//>
|
|
604
|
+
<//>
|
|
496
605
|
<${TableCell}>
|
|
497
606
|
<${Typography} variant="caption" color="text.secondary">
|
|
498
607
|
${ev.agentType || "–"}
|
|
@@ -608,6 +717,15 @@ export function TelemetryTab() {
|
|
|
608
717
|
value=${formatDurationMs(lifetimeTotals?.durationMs || 0)} />
|
|
609
718
|
<//>
|
|
610
719
|
|
|
720
|
+
${data?.diagnostics?.agentRunSource ? html`
|
|
721
|
+
<${Alert} severity="info" sx=${{ mb: 2 }}>
|
|
722
|
+
Agent Runs are currently sourced from ${data.diagnostics.agentRunSource === "completed_sessions" ? "the persistent completed-session ledger" : "session-start telemetry"}.
|
|
723
|
+
${data.diagnostics.agentRunSource === "completed_sessions"
|
|
724
|
+
? ` Counted ${formatCount(data.diagnostics.completedSessions || 0)} completed sessions in this window.`
|
|
725
|
+
: ` Counted ${formatCount(data.diagnostics.sessionStarts || 0)} session-start events in this window.`}
|
|
726
|
+
<//>
|
|
727
|
+
` : null}
|
|
728
|
+
|
|
611
729
|
<!-- Activity trend chart -->
|
|
612
730
|
<${Paper} elevation=${1} sx=${{ p: 2, mb: 2 }}>
|
|
613
731
|
<${Typography} variant="h6" gutterBottom>Activity Trend<//>
|
|
@@ -35,6 +35,118 @@ function getLabel(type) {
|
|
|
35
35
|
return String(type || "").split(".").pop()?.replace(/_/g, " ") || String(type || "");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function normalizePreviewLine(text, maxLength = 84) {
|
|
39
|
+
const compact = String(text || "").replace(/\s+/g, " ").trim();
|
|
40
|
+
if (!compact) return "";
|
|
41
|
+
return compact.length > maxLength
|
|
42
|
+
? `${compact.slice(0, Math.max(0, maxLength - 1))}…`
|
|
43
|
+
: compact;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function collectPreviewText(value, lines = [], seen = new Set(), depth = 0) {
|
|
47
|
+
if (value == null || depth > 2 || lines.length >= 6) return lines;
|
|
48
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
49
|
+
const rawLines = String(value)
|
|
50
|
+
.split(/\r?\n/)
|
|
51
|
+
.map((line) => normalizePreviewLine(line))
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
for (const line of rawLines) {
|
|
54
|
+
if (seen.has(line)) continue;
|
|
55
|
+
seen.add(line);
|
|
56
|
+
lines.push(line);
|
|
57
|
+
if (lines.length >= 6) break;
|
|
58
|
+
}
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
for (const item of value.slice(0, 4)) {
|
|
63
|
+
collectPreviewText(item, lines, seen, depth + 1);
|
|
64
|
+
if (lines.length >= 6) break;
|
|
65
|
+
}
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
68
|
+
if (typeof value !== "object") return lines;
|
|
69
|
+
|
|
70
|
+
const prioritizedKeys = [
|
|
71
|
+
"summary",
|
|
72
|
+
"narrative",
|
|
73
|
+
"text",
|
|
74
|
+
"message",
|
|
75
|
+
"content",
|
|
76
|
+
"output",
|
|
77
|
+
"result",
|
|
78
|
+
"answer",
|
|
79
|
+
"response",
|
|
80
|
+
"stdout",
|
|
81
|
+
"stderr",
|
|
82
|
+
];
|
|
83
|
+
let matched = false;
|
|
84
|
+
for (const key of prioritizedKeys) {
|
|
85
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
|
|
86
|
+
matched = true;
|
|
87
|
+
collectPreviewText(value[key], lines, seen, depth + 1);
|
|
88
|
+
if (lines.length >= 6) return lines;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(value.lines)) {
|
|
91
|
+
matched = true;
|
|
92
|
+
collectPreviewText(value.lines, lines, seen, depth + 1);
|
|
93
|
+
}
|
|
94
|
+
if (value.preview && typeof value.preview === "object") {
|
|
95
|
+
matched = true;
|
|
96
|
+
collectPreviewText(value.preview, lines, seen, depth + 1);
|
|
97
|
+
}
|
|
98
|
+
if (!matched) {
|
|
99
|
+
try {
|
|
100
|
+
const json = JSON.stringify(value);
|
|
101
|
+
if (json) collectPreviewText(json, lines, seen, depth + 1);
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
return lines;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function extractPreviewTokenCount(value) {
|
|
108
|
+
if (!value || typeof value !== "object") return null;
|
|
109
|
+
const candidates = [
|
|
110
|
+
value.tokenCount,
|
|
111
|
+
value.totalTokens,
|
|
112
|
+
value.total_tokens,
|
|
113
|
+
value.tokens,
|
|
114
|
+
value.usage?.total_tokens,
|
|
115
|
+
value.usage?.totalTokens,
|
|
116
|
+
value.tokenUsage?.totalTokens,
|
|
117
|
+
value.metrics?.total_tokens,
|
|
118
|
+
value.metrics?.totalTokens,
|
|
119
|
+
];
|
|
120
|
+
for (const candidate of candidates) {
|
|
121
|
+
const parsed = Number(candidate);
|
|
122
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
123
|
+
return Math.max(0, Math.round(parsed));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const input = Number(
|
|
127
|
+
value.inputTokens
|
|
128
|
+
?? value.input_tokens
|
|
129
|
+
?? value.promptTokens
|
|
130
|
+
?? value.prompt_tokens
|
|
131
|
+
?? value.usage?.prompt_tokens
|
|
132
|
+
?? value.usage?.inputTokens
|
|
133
|
+
?? value.tokenUsage?.inputTokens,
|
|
134
|
+
);
|
|
135
|
+
const output = Number(
|
|
136
|
+
value.outputTokens
|
|
137
|
+
?? value.output_tokens
|
|
138
|
+
?? value.completionTokens
|
|
139
|
+
?? value.completion_tokens
|
|
140
|
+
?? value.usage?.completion_tokens
|
|
141
|
+
?? value.usage?.outputTokens
|
|
142
|
+
?? value.tokenUsage?.outputTokens,
|
|
143
|
+
);
|
|
144
|
+
if (Number.isFinite(input) || Number.isFinite(output)) {
|
|
145
|
+
return Math.max(0, Math.round((Number.isFinite(input) ? input : 0) + (Number.isFinite(output) ? output : 0)));
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
38
150
|
export function createGraphSnapshot(nodes = [], edges = []) {
|
|
39
151
|
return {
|
|
40
152
|
nodes: normalizeGraphValue(nodes),
|
|
@@ -217,3 +329,21 @@ export function buildNodeStatusesFromRunDetail(run) {
|
|
|
217
329
|
|
|
218
330
|
return statuses;
|
|
219
331
|
}
|
|
332
|
+
|
|
333
|
+
export function resolveNodeOutputPreview(_nodeType, livePreview = null, rawOutput = null) {
|
|
334
|
+
const liveLines = Array.isArray(livePreview?.lines)
|
|
335
|
+
? livePreview.lines.map((line) => normalizePreviewLine(line)).filter(Boolean)
|
|
336
|
+
: [];
|
|
337
|
+
const liveTokenCount = extractPreviewTokenCount(livePreview);
|
|
338
|
+
if (liveLines.length || liveTokenCount != null) {
|
|
339
|
+
return {
|
|
340
|
+
lines: liveLines.slice(0, 3),
|
|
341
|
+
tokenCount: liveTokenCount,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
lines: collectPreviewText(rawOutput).slice(0, 3),
|
|
347
|
+
tokenCount: extractPreviewTokenCount(rawOutput),
|
|
348
|
+
};
|
|
349
|
+
}
|