pi-agent-flow 2.0.7 → 2.0.9

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 (41) hide show
  1. package/agents/audit.md +2 -2
  2. package/agents/trace.md +3 -17
  3. package/dist/batch/execute.d.ts.map +1 -1
  4. package/dist/batch/execute.js +8 -0
  5. package/dist/batch/execute.js.map +1 -1
  6. package/dist/batch/render.js +3 -3
  7. package/dist/batch/render.js.map +1 -1
  8. package/dist/flow/agents.d.ts.map +1 -1
  9. package/dist/flow/agents.js +1 -0
  10. package/dist/flow/agents.js.map +1 -1
  11. package/dist/flow/runner.d.ts.map +1 -1
  12. package/dist/flow/runner.js +2 -0
  13. package/dist/flow/runner.js.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +20 -16
  16. package/dist/index.js.map +1 -1
  17. package/dist/steering/sliding-prompt.d.ts.map +1 -1
  18. package/dist/steering/sliding-prompt.js +4 -3
  19. package/dist/steering/sliding-prompt.js.map +1 -1
  20. package/dist/tools/trace.d.ts +25 -16
  21. package/dist/tools/trace.d.ts.map +1 -1
  22. package/dist/tools/trace.js +195 -130
  23. package/dist/tools/trace.js.map +1 -1
  24. package/dist/tui/flow-colors.d.ts +6 -1
  25. package/dist/tui/flow-colors.d.ts.map +1 -1
  26. package/dist/tui/flow-colors.js +1 -0
  27. package/dist/tui/flow-colors.js.map +1 -1
  28. package/dist/tui/render.d.ts +0 -8
  29. package/dist/tui/render.d.ts.map +1 -1
  30. package/dist/tui/render.js +49 -166
  31. package/dist/tui/render.js.map +1 -1
  32. package/dist/tui/scramble/algorithm.d.ts.map +1 -1
  33. package/dist/tui/scramble/algorithm.js +9 -12
  34. package/dist/tui/scramble/algorithm.js.map +1 -1
  35. package/dist/tui/scramble/constants.d.ts +3 -3
  36. package/dist/tui/scramble/constants.js +2 -2
  37. package/dist/types/ui.d.ts +4 -0
  38. package/dist/types/ui.d.ts.map +1 -1
  39. package/dist/types/ui.js +41 -0
  40. package/dist/types/ui.js.map +1 -1
  41. package/package.json +1 -1
@@ -24,6 +24,14 @@ function getAnonymousFlowId() {
24
24
  export function resetAnonymousFlowIdCounter() {
25
25
  anonFlowIdCounter = 0;
26
26
  }
27
+ function getContentRole(baseRole, text, useError) {
28
+ if (useError)
29
+ return "msgError";
30
+ if (["[awaiting...]", "[n/a]", "[skipped]", "[approved]", "[finished]"].includes(text)) {
31
+ return "placeholder";
32
+ }
33
+ return baseRole;
34
+ }
27
35
  function getLiveTextWithFallback(id) {
28
36
  const value = getLiveText(id);
29
37
  if (value !== undefined)
@@ -31,7 +39,7 @@ function getLiveTextWithFallback(id) {
31
39
  const fallbackId = id.includes("#") ? "collapsed" + id.slice(id.indexOf("#")) : "collapsed";
32
40
  return getLiveText(fallbackId);
33
41
  }
34
- import { formatCompactStats, formatFlowTypeName, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi, formatModelLabel, formatContextLabel, formatTps, italic } from "./render-utils.js";
42
+ import { formatCompactStats, formatFlowTypeName, lowerFirstWord, truncateChars, tailText, getTruncationBudget, visibleLength, stripAnsi, formatModelLabel, formatContextLabel, formatTps } from "./render-utils.js";
35
43
  function shortenPath(p) {
36
44
  const home = os.homedir();
37
45
  return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
@@ -395,12 +403,14 @@ export function renderFlowResult(result, expanded, theme, args, config) {
395
403
  type: flowRequest.type || "unknown",
396
404
  agentSource: "user",
397
405
  intent: flowRequest.intent || "Processing...",
398
- aim: flowRequest.aim || flowRequest.intent || "Processing...",
406
+ aim: flowRequest.aim ?? flowRequest.intent ?? "Processing...",
399
407
  acceptance: flowRequest.acceptance,
400
408
  exitCode: -1, // In progress
401
409
  messages: [],
402
410
  stderr: "",
403
411
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0, toolCalls: 0 },
412
+ ...(flowRequest.model ? { model: flowRequest.model } : {}),
413
+ ...(flowRequest.maxContextTokens !== undefined ? { maxContextTokens: flowRequest.maxContextTokens } : {}),
404
414
  };
405
415
  const ghostId = resolvedToolCallId || 'ghost';
406
416
  if (expanded) {
@@ -473,140 +483,6 @@ export function renderFlowResult(result, expanded, theme, args, config) {
473
483
  return container;
474
484
  }
475
485
  // ---------------------------------------------------------------------------
476
- // Trace rendering — simplified, no model, inline stats
477
- // ---------------------------------------------------------------------------
478
- export function renderTraceCall(_args, _theme, _config) {
479
- // Trace call frame is invisible — the result frame shows 'trace <aim>'.
480
- // Returning an empty Container avoids a duplicate 'trace' line.
481
- return new Container();
482
- }
483
- export function renderTraceResult(result, expanded, theme, args, config) {
484
- const details = result.details;
485
- const streamingText = result.content?.[0]?.type === "text" ? result.content[0].text : undefined;
486
- // Resolve id (same pattern as renderFlowResult)
487
- let resolvedToolCallId;
488
- if (args?.state) {
489
- const s = args.state;
490
- resolvedToolCallId = s.__widgetId;
491
- if (!resolvedToolCallId) {
492
- resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
493
- if (!resolvedToolCallId) {
494
- resolvedToolCallId = getAnonymousFlowId();
495
- }
496
- s.__widgetId = resolvedToolCallId;
497
- }
498
- }
499
- else {
500
- resolvedToolCallId = result._toolCallId || args?.toolCallId || args?.id;
501
- }
502
- const id = resolvedToolCallId || "trace";
503
- const now = Date.now();
504
- let container = new Container();
505
- // Get the SingleResult
506
- let r;
507
- if (details?.results && details.results.length > 0) {
508
- r = details.results[0];
509
- }
510
- const isComplete = r ? isFlowStatusComplete(r) : false;
511
- // Header line: ● trace · <aim> · <stats>
512
- const typeName = formatFlowTypeName("trace");
513
- const aimText = r?.aim || r?.intent || streamingText || "trace";
514
- const initialDot = r ? flowStatusIcon(r, theme) : theme.fg("success", "●");
515
- const dotPlaceholder = stripAnsi(initialDot) + " ";
516
- const statsParts = [];
517
- if (r) {
518
- if (r.maxContextTokens !== undefined || r.usage.contextTokens > 0) {
519
- statsParts.push(formatContextLabel(r.usage.contextTokens, r.maxContextTokens));
520
- }
521
- statsParts.push(formatTps(r.usage.smoothedTps));
522
- }
523
- const displayStats = statsParts.length > 0 ? " · " + statsParts.join(" · ") : "";
524
- const statsPlain = stripAnsi(displayStats);
525
- const headerPlain = `${dotPlaceholder}${typeName}${statsPlain}`;
526
- const headerSegments = [
527
- { text: dotPlaceholder, style: (_s) => (r ? getScintillatingStatusDot(r, theme, Date.now(), id) : initialDot) + " " },
528
- { text: typeName, style: (s) => applyRole("flowName", s, theme, config) },
529
- ];
530
- if (statsPlain) {
531
- headerSegments.push({ text: displayStats, style: (s) => applyRole("stats", s, theme, config) });
532
- }
533
- container.addChild(new DynamicScrambleText(`${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("stats", displayStats, theme, config)}`, () => {
534
- const now2 = Date.now();
535
- const result2 = scrambleManager.updateText(id, "header", headerPlain, now2, isComplete, true);
536
- return reconstructHeader(result2.content, headerSegments);
537
- }, true));
538
- // Cmd line: └─ cmd ▸ <last tool call>
539
- const actTree = "└─";
540
- const actLabel = ` cmd ▸ `;
541
- if (r?.messages && r.messages.length > 0) {
542
- const lastTool = getLastToolCall(r.messages);
543
- const actStr = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
544
- const actFullText = stripAnsi(lowerFirstWord(actStr));
545
- const actInitial = `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", italic(actFullText), theme, config)}`;
546
- container.addChild(new DynamicScrambleText(actInitial, () => {
547
- const now2 = Date.now();
548
- const freshAct = lastTool ? formatFlowToolCall(lastTool.name, lastTool.args, theme.fg.bind(theme)) : "[n/a]";
549
- const freshPlain = stripAnsi(lowerFirstWord(freshAct));
550
- const result2 = scrambleManager.updateAct(id, freshPlain, now2, isComplete, true);
551
- const content = result2.content;
552
- return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("actContent", italic(content), theme, config)}`;
553
- }, true));
554
- }
555
- else {
556
- // No messages yet — show awaiting
557
- const actInitial = `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole("prefixLabel", "[awaiting...]", theme, config)}`;
558
- container.addChild(new DynamicScrambleText(actInitial, () => {
559
- const now2 = Date.now();
560
- const plain = "[awaiting...]";
561
- const result2 = scrambleManager.updateAct(id, plain, now2, isComplete, true);
562
- const content = result2.content;
563
- return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole((r && isFlowAwaiting(r)) ? "prefixLabel" : "actContent", italic(content), theme, config)}`;
564
- }, true));
565
- }
566
- // Expanded view: add full output
567
- if (expanded) {
568
- const flowOutput = streamingText;
569
- if (flowOutput) {
570
- container.addChild(new Spacer(1));
571
- container.addChild(new Markdown(flowOutput, 0, 0, getMarkdownTheme()));
572
- }
573
- }
574
- // In-place mutation pattern (same as renderFlowResult)
575
- if (args?.state) {
576
- const s = args.state;
577
- if (!s.__rootContainer) {
578
- if (container instanceof Container) {
579
- s.__rootContainer = container;
580
- }
581
- else {
582
- const root = new Container();
583
- root.addChild(container);
584
- s.__rootContainer = root;
585
- }
586
- }
587
- else if (container !== s.__rootContainer) {
588
- const root = s.__rootContainer;
589
- root.clear();
590
- if (container instanceof Container) {
591
- const children = [...container.children];
592
- for (const child of children) {
593
- root.addChild(child);
594
- }
595
- }
596
- else {
597
- root.addChild(container);
598
- }
599
- root.invalidate();
600
- container = root;
601
- }
602
- }
603
- if (isComplete) {
604
- scrambleManager.completeFlow(id);
605
- }
606
- runScrambleTimer(args, id);
607
- return container;
608
- }
609
- // ---------------------------------------------------------------------------
610
486
  // Single flow result
611
487
  // ---------------------------------------------------------------------------
612
488
  export function renderSingleFlowResult(r, expanded, theme, streamingText, toolCallId, config) {
@@ -735,16 +611,17 @@ function renderFlowExpanded(r, icon, error, displayItems, flowOutput, theme, id,
735
611
  }
736
612
  // Output: animate streaming text; show clean markdown when complete
737
613
  if (isFlowAwaiting(r)) {
738
- container.addChild(new Text(applyRole("prefixLabel", "[awaiting...]", theme, config), 0, 0));
614
+ container.addChild(new Text(applyRole("placeholder", "[awaiting...]", theme, config), 0, 0));
739
615
  }
740
616
  else if (!isComplete && streamingText != null) {
741
617
  const msgBudget = getTruncationBudget(0);
742
618
  const displayMsg = tailText(stripAnsi(streamingText), msgBudget);
743
- container.addChild(new DynamicScrambleText(displayMsg, () => {
619
+ container.addChild(new DynamicScrambleText(applyRole("msgContent", displayMsg, theme, config), () => {
744
620
  const budget = getTruncationBudget(0);
745
621
  const freshStreamingText = getLiveTextWithFallback(id) ?? streamingText;
746
622
  const text = tailText(stripAnsi(freshStreamingText), budget);
747
- return scrambleManager.updateMsg(id, text, Date.now(), isComplete, undefined, true).content;
623
+ const result = scrambleManager.updateMsg(id, text, Date.now(), isComplete, undefined, true);
624
+ return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", text, theme, config);
748
625
  }));
749
626
  }
750
627
  else if (flowOutput) {
@@ -798,12 +675,13 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
798
675
  const ctxLabel = formatContextLabel(r.usage.contextTokens, r.maxContextTokens);
799
676
  statsParts.push(ctxLabel);
800
677
  }
801
- const tpsFormatted = formatTps(r.usage.smoothedTps);
802
- statsParts.push(tpsFormatted);
803
678
  let displayStats = statsParts.join(" · ");
804
- // Flash TPS value when it changes
805
- const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
806
679
  if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
680
+ const tpsFormatted = formatTps(r.usage.smoothedTps);
681
+ statsParts.push(tpsFormatted);
682
+ displayStats = statsParts.join(" · ");
683
+ // Flash TPS value when it changes
684
+ const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
807
685
  const scrambledTps = scrambleManager.updateTps(id, tpsNum, now, isComplete, true);
808
686
  if (scrambledTps !== tpsNum) {
809
687
  displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
@@ -833,20 +711,20 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
833
711
  return reconstructHeader(result.content, headerSegments);
834
712
  }, true));
835
713
  // aim: line — glitch on text change
836
- if (r.aim) {
714
+ if (r.aim && r.type !== "trace") {
837
715
  const aimTree = "├─";
838
716
  const aimLabel = ` aim ▸ `;
839
717
  const aimPrefix = `${aimTree}${aimLabel}`;
840
718
  const budget = getTruncationBudget(visibleLength(aimPrefix));
841
719
  const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
842
- container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(displayAim), theme, config)}`, () => {
720
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(getContentRole("aimContent", displayAim), displayAim, theme, config)}`, () => {
843
721
  const now = Date.now();
844
722
  const freshAimLabel = ` aim ▸ `;
845
723
  const freshAimPrefix = `${aimTree}${freshAimLabel}`;
846
724
  const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
847
725
  const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
848
726
  const result = scrambleManager.updateAim(id, freshText, now, isComplete, true);
849
- return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(result.content), theme, config)}`;
727
+ return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(getContentRole("aimContent", freshText), result.content, theme, config)}`;
850
728
  }, true));
851
729
  }
852
730
  // act: line (last tool call with count)
@@ -859,14 +737,14 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
859
737
  const budget = getTruncationBudget(visibleLength(prefixStub));
860
738
  const actFullText = stripAnsi(lowerFirstWord(actStr));
861
739
  const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
862
- container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(initialActContent), theme, config)}`, () => {
740
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", initialActContent), initialActContent, theme, config)}`, () => {
863
741
  const now = Date.now();
864
742
  const actLabel = ` cmd ▸ `;
865
743
  const actPrefix = `${actTree}${actLabel}`;
866
744
  const freshBudget = getTruncationBudget(visibleLength(actPrefix));
867
745
  const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
868
746
  const actContent = scrambleManager.updateAct(id, displayAct, now, isComplete, true).content;
869
- return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(actContent), theme, config)}`;
747
+ return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", displayAct), actContent, theme, config)}`;
870
748
  }, true));
871
749
  // msg: line (last assistant text or streaming) — full mode only
872
750
  if (!isLite) {
@@ -910,7 +788,7 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
910
788
  const msgTree = "└─";
911
789
  const msgLabel = ` msg ▸ `;
912
790
  const initialMsgPrefix = `${msgTree}${msgLabel}`;
913
- container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(initialMsgContent), theme, config)}`, () => {
791
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", initialMsgContent, useError), initialMsgContent, theme, config)}`, () => {
914
792
  const now = Date.now();
915
793
  const msgLabel = ` msg ▸ `;
916
794
  const msgPrefix = `${msgTree}${msgLabel}`;
@@ -931,7 +809,7 @@ function renderFlowCollapsed(r, icon, error, flowOutput, theme, streamingText, t
931
809
  }
932
810
  const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
933
811
  const result = scrambleManager.updateMsg(id, displayMsg, now, isComplete, undefined, true);
934
- return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(result.content), theme, config)}`;
812
+ return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", freshRawMsg, useError), result.content, theme, config)}`;
935
813
  }, true));
936
814
  }
937
815
  if (isComplete) {
@@ -1003,17 +881,18 @@ function renderMultiFlowExpanded(results, successCount, icon, theme, baseId, now
1003
881
  }
1004
882
  // Output: animate streaming text; show clean markdown when complete
1005
883
  if (isFlowAwaiting(r)) {
1006
- container.addChild(new Text(applyRole("prefixLabel", "[awaiting...]", theme, config), 0, 0));
884
+ container.addChild(new Text(applyRole("placeholder", "[awaiting...]", theme, config), 0, 0));
1007
885
  }
1008
886
  else if (!isComplete && r.streamingText != null) {
1009
887
  const streamingRaw = r.streamingText;
1010
888
  const msgBudget = getTruncationBudget(0);
1011
889
  const displayMsg = tailText(stripAnsi(streamingRaw), msgBudget);
1012
- container.addChild(new DynamicScrambleText(displayMsg, () => {
890
+ container.addChild(new DynamicScrambleText(applyRole("msgContent", displayMsg, theme, config), () => {
1013
891
  const budget = getTruncationBudget(0);
1014
892
  const freshStreamingText = getLiveTextWithFallback(flowId) ?? streamingRaw;
1015
893
  const text = tailText(stripAnsi(freshStreamingText), budget);
1016
- return scrambleManager.updateMsg(flowId, text, Date.now(), isComplete, undefined, true).content;
894
+ const result = scrambleManager.updateMsg(flowId, text, Date.now(), isComplete, undefined, true);
895
+ return result.isAnimating ? applyRole("msgContent", result.content, theme, config) : applyRole("msgContent", text, theme, config);
1017
896
  }));
1018
897
  }
1019
898
  else if (flowOutput) {
@@ -1168,17 +1047,18 @@ function renderFlowHeader(container, r, flowId, headerPrefix, theme, now, config
1168
1047
  const ctxLabel = formatContextLabel(r.usage.contextTokens, r.maxContextTokens);
1169
1048
  statsParts.push(ctxLabel);
1170
1049
  }
1171
- const tpsFormatted = formatTps(r.usage.smoothedTps);
1172
- statsParts.push(tpsFormatted);
1173
1050
  let displayStats = statsParts.join(" · ");
1174
- const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
1175
1051
  if (r.usage.smoothedTps && r.usage.smoothedTps > 0) {
1052
+ const tpsFormatted = formatTps(r.usage.smoothedTps);
1053
+ statsParts.push(tpsFormatted);
1054
+ displayStats = statsParts.join(" · ");
1055
+ const tpsNum = tpsFormatted.slice(0, -4); // remove " t/s" suffix
1176
1056
  const scrambledTps = scrambleManager.updateTps(flowId, tpsNum, now, flowComplete, true);
1177
1057
  if (scrambledTps !== tpsNum) {
1178
1058
  displayStats = displayStats.replace(`${tpsNum} t/s`, `${scrambledTps} t/s`);
1179
1059
  }
1180
1060
  }
1181
- const modelSegment = modelLabel ? ` · ${modelLabel}` : "";
1061
+ const modelSegment = modelLabel ? ` ${modelLabel}` : "";
1182
1062
  const statsSegment = ` · ${displayStats}`;
1183
1063
  const statsPlain = stripAnsi(statsSegment);
1184
1064
  headerLine = `${applyRole("treeChars", headerPrefix, theme, config)} ${initialDot} ${applyRole("flowName", typeName, theme, config)}${applyRole("modelName", modelSegment, theme, config)}${applyRole("stats", statsSegment, theme, config)}`;
@@ -1201,20 +1081,20 @@ function renderFlowBody(container, r, flowId, indent, theme, now, config) {
1201
1081
  const isComplete = isFlowStatusComplete(r);
1202
1082
  const flowComplete = isComplete;
1203
1083
  // aim: line — glitch on text change
1204
- if (r.aim) {
1084
+ if (r.aim && r.type !== "trace") {
1205
1085
  const aimTree = indent + "├─";
1206
1086
  const aimLabel = ` aim ▸ `;
1207
1087
  const aimPrefix = `${aimTree}${aimLabel}`;
1208
1088
  const budget = getTruncationBudget(visibleLength(aimPrefix));
1209
1089
  const displayAim = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), budget);
1210
- container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(displayAim), theme, config)}`, () => {
1090
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", aimLabel, theme, config)}${applyRole(getContentRole("aimContent", displayAim), displayAim, theme, config)}`, () => {
1211
1091
  const now = Date.now();
1212
1092
  const freshAimLabel = ` aim ▸ `;
1213
1093
  const freshAimPrefix = `${aimTree}${freshAimLabel}`;
1214
1094
  const freshBudget = getTruncationBudget(visibleLength(freshAimPrefix));
1215
1095
  const freshText = isFlowAwaiting(r) ? "[awaiting...]" : truncateChars(lowerFirstWord(r.aim), freshBudget);
1216
1096
  const result = scrambleManager.updateAim(flowId, freshText, now, flowComplete, true);
1217
- return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "aimContent", italic(result.content), theme, config)}`;
1097
+ return `${applyRole("treeChars", aimTree, theme, config)}${applyRole("prefixLabel", freshAimLabel, theme, config)}${applyRole(getContentRole("aimContent", freshText), result.content, theme, config)}`;
1218
1098
  }, true));
1219
1099
  }
1220
1100
  // act: line (last tool call with count)
@@ -1227,14 +1107,14 @@ function renderFlowBody(container, r, flowId, indent, theme, now, config) {
1227
1107
  const budget = getTruncationBudget(visibleLength(prefixStub));
1228
1108
  const actFullText = stripAnsi(lowerFirstWord(actStr));
1229
1109
  const initialActContent = isFlowAwaiting(r) ? "[n/a]" : (actFullText.length > budget ? tailText(actFullText, budget) : actFullText);
1230
- container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(initialActContent), theme, config)}`, () => {
1110
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", initialActContent), initialActContent, theme, config)}`, () => {
1231
1111
  const now = Date.now();
1232
1112
  const actLabel = ` cmd ▸ `;
1233
1113
  const actPrefix = `${actTree}${actLabel}`;
1234
1114
  const freshBudget = getTruncationBudget(visibleLength(actPrefix));
1235
1115
  const displayAct = isFlowAwaiting(r) ? "[n/a]" : tailText(actFullText, freshBudget);
1236
1116
  const actContent = scrambleManager.updateAct(flowId, displayAct, now, flowComplete, true).content;
1237
- return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(isFlowAwaiting(r) ? "prefixLabel" : "actContent", italic(actContent), theme, config)}`;
1117
+ return `${applyRole("treeChars", actTree, theme, config)}${applyRole("prefixLabel", actLabel, theme, config)}${applyRole(getContentRole("actContent", displayAct), actContent, theme, config)}`;
1238
1118
  }, true));
1239
1119
  // msg: line (live streaming text or last assistant text) — full mode only
1240
1120
  if (!isLite) {
@@ -1255,12 +1135,15 @@ function renderFlowBody(container, r, flowId, indent, theme, now, config) {
1255
1135
  rawMsg = stripAnsi(r.errorMessage);
1256
1136
  useError = true;
1257
1137
  }
1258
- else if (r.pingPongMeta && r.pingPongMeta.finalVerdict === "pass") {
1138
+ else if ((r.pingPongMeta?.finalVerdict === "pass" || r.structuredOutput?.verdict === "pass") && r.type === "audit") {
1259
1139
  rawMsg = "[approved]";
1260
1140
  }
1261
- else {
1141
+ else if (r.type === "audit" && !r.structuredOutput?.summary && !getFlowOutput(r.messages)) {
1262
1142
  rawMsg = "[finished]";
1263
1143
  }
1144
+ else {
1145
+ rawMsg = stripAnsi(r.structuredOutput?.summary ?? getFlowOutput(r.messages) ?? getFlowSummaryText(r)) || "[n/a]";
1146
+ }
1264
1147
  }
1265
1148
  else {
1266
1149
  const liveMsgText = isFlowRunning(r) ? getLiveTextWithFallback(flowId) : undefined;
@@ -1290,7 +1173,7 @@ function renderFlowBody(container, r, flowId, indent, theme, now, config) {
1290
1173
  }
1291
1174
  const initialNeedsTail = !isFlowAwaiting(r) && isFlowRunning(r) && (r.streamingText != null || getLiveTextWithFallback(flowId) != null);
1292
1175
  const initialDisplayMsg = initialNeedsTail ? tailText(rawMsg, msgBudget) : truncateChars(rawMsg, msgBudget);
1293
- container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(initialDisplayMsg), theme, config)}`, () => {
1176
+ container.addChild(new DynamicScrambleText(`${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", initialDisplayMsg, useError), initialDisplayMsg, theme, config)}`, () => {
1294
1177
  const now = Date.now();
1295
1178
  const msgLabel = ` msg ▸ `;
1296
1179
  const msgPrefix = `${msgTree}${msgLabel}`;
@@ -1311,7 +1194,7 @@ function renderFlowBody(container, r, flowId, indent, theme, now, config) {
1311
1194
  }
1312
1195
  const displayMsg = needsTail ? tailText(freshRawMsg, msgBudget) : truncateChars(freshRawMsg, msgBudget);
1313
1196
  const result = scrambleManager.updateMsg(flowId, displayMsg, now, flowComplete, undefined, true);
1314
- return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(useError ? "msgError" : "msgContent", italic(result.content), theme, config)}`;
1197
+ return `${applyRole("treeChars", msgTree, theme, config)}${applyRole("prefixLabel", msgLabel, theme, config)}${applyRole(getContentRole("msgContent", freshRawMsg, useError), result.content, theme, config)}`;
1315
1198
  }, true));
1316
1199
  }
1317
1200
  }