@wrongstack/tui 0.8.0 → 0.8.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/dist/index.js CHANGED
@@ -721,6 +721,10 @@ function fmtTokens(n) {
721
721
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
722
722
  return `${(n / 1e6).toFixed(1)}M`;
723
723
  }
724
+ function fmtCost2(n) {
725
+ if (n === 0) return "\u2014";
726
+ return `$${n.toFixed(3)}`;
727
+ }
724
728
  function FleetMonitor({
725
729
  entries,
726
730
  totalCost,
@@ -730,17 +734,19 @@ function FleetMonitor({
730
734
  }) {
731
735
  const all = Object.values(entries);
732
736
  const running = all.filter((e) => e.status === "running");
737
+ const idle = all.filter((e) => e.status === "idle").length;
733
738
  const done = all.filter((e) => e.status === "success").length;
734
739
  const failed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
735
740
  const concurrencyRatio = maxConcurrent > 0 ? running.length / maxConcurrent : 0;
736
- const maxTools = Math.max(1, ...all.map((e) => e.toolCalls));
737
741
  const ordered = [...all].sort((a, b) => {
738
742
  const ra = a.status === "running" ? 0 : a.status === "idle" ? 1 : 2;
739
743
  const rb = b.status === "running" ? 0 : b.status === "idle" ? 1 : 2;
740
744
  if (ra !== rb) return ra - rb;
745
+ if (ra === 2) return b.lastEventAt - a.lastEventAt;
741
746
  return a.startedAt - b.startedAt;
742
747
  });
743
- const shown = ordered.slice(0, 8);
748
+ const shown = ordered.slice(0, 12);
749
+ const overflow = all.length - shown.length;
744
750
  const events = [];
745
751
  for (const e of all) {
746
752
  events.push({ at: e.startedAt, icon: "\u25CF", color: "cyan", text: `${e.name} spawned` });
@@ -763,15 +769,19 @@ function FleetMonitor({
763
769
  }
764
770
  }
765
771
  events.sort((a, b) => b.at - a.at);
766
- const timeline = events.slice(0, 6);
772
+ const timeline = events.slice(0, 8);
767
773
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
768
774
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
769
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "FLEET MONITOR" }),
775
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "FLEET \xB7 ORCHESTRATION" }),
770
776
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
771
777
  /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
772
778
  "\u25B6",
773
779
  running.length
774
780
  ] }),
781
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
782
+ "\u25CB",
783
+ idle
784
+ ] }),
775
785
  /* @__PURE__ */ jsxs(Text, { color: "green", children: [
776
786
  "\u2713",
777
787
  done
@@ -786,7 +796,7 @@ function FleetMonitor({
786
796
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "concurrency" }),
787
797
  /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
788
798
  "[",
789
- renderProgress(concurrencyRatio, 10),
799
+ renderProgress(concurrencyRatio, 12),
790
800
  "]"
791
801
  ] }),
792
802
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
@@ -804,36 +814,33 @@ function FleetMonitor({
804
814
  /* @__PURE__ */ jsx(Text, { color: "green", children: ` $${totalCost.toFixed(3)}` })
805
815
  ] }),
806
816
  shown.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No subagents yet \u2014 spawn with /fleet spawn or /fleet dispatch." }) : null,
807
- shown.map((e) => {
808
- const s = STATUS[e.status];
809
- const elapsed = e.status === "running" ? fmtElapsed(Math.max(0, nowTick - e.startedAt)) : e.status;
810
- const spark = sparkline(bucketActivity(e.recentTools, nowTick));
811
- const tool = e.currentTool?.name ?? e.recentTools[e.recentTools.length - 1]?.name ?? "";
812
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
813
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
814
- /* @__PURE__ */ jsx(Text, { color: s.color, bold: true, children: s.icon }),
815
- /* @__PURE__ */ jsx(Text, { bold: true, children: e.name.padEnd(12).slice(0, 12) }),
816
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed.padEnd(7).slice(0, 7) }),
817
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: renderProgress(e.toolCalls / maxTools, 10) }),
818
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
819
- "L",
820
- e.iterations,
821
- " ",
822
- e.toolCalls,
823
- "t"
824
- ] }),
817
+ shown.length > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
818
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
819
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
820
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "name".padEnd(16) }),
821
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "status".padEnd(10) }),
822
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "L/t".padEnd(8) }),
823
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "elapsed".padEnd(8) }),
824
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "cost" })
825
+ ] }),
826
+ shown.map((e) => {
827
+ const s = STATUS[e.status];
828
+ const elapsed = e.status === "running" ? fmtElapsed(Math.max(0, nowTick - e.startedAt)) : fmtElapsed(Math.max(0, nowTick - e.lastEventAt)) + " ago";
829
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
830
+ /* @__PURE__ */ jsx(Text, { color: s.color, children: s.icon }),
831
+ /* @__PURE__ */ jsx(Text, { children: e.name.padEnd(16).slice(0, 16) }),
832
+ /* @__PURE__ */ jsx(Text, { color: s.color, children: e.status.padEnd(10) }),
833
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `L${e.iterations} ${e.toolCalls}t`.padEnd(8) }),
834
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed.padEnd(8).slice(0, 8) }),
835
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: fmtCost2(e.cost) }),
825
836
  e.extensions && e.extensions > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
826
- "\u26A1\xD7",
837
+ " \u26A1\xD7",
827
838
  e.extensions
828
839
  ] }) : null
829
- ] }),
830
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
831
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
832
- /* @__PURE__ */ jsx(Text, { color: "green", children: spark }),
833
- tool ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: tool }) : null
834
- ] })
835
- ] }, e.id);
836
- }),
840
+ ] }, e.id);
841
+ }),
842
+ overflow > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2026 +${overflow} more` }) : null
843
+ ] }) : null,
837
844
  timeline.length > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
838
845
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "timeline" }),
839
846
  timeline.map((ev, i) => (
@@ -855,11 +862,19 @@ var STATUS2 = {
855
862
  timeout: { icon: "\u23F1", color: "yellow" },
856
863
  stopped: { icon: "\u2298", color: "gray" }
857
864
  };
865
+ function isTerminal(status) {
866
+ return status === "success" || status === "failed" || status === "timeout" || status === "stopped";
867
+ }
858
868
  function fmtTokens2(n) {
859
869
  if (n < 1e3) return String(n);
860
870
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
861
871
  return `${(n / 1e6).toFixed(1)}M`;
862
872
  }
873
+ function snippet(s, max = 72) {
874
+ const oneLine2 = s.replace(/\s+/g, " ").trim();
875
+ if (oneLine2.length <= max) return oneLine2;
876
+ return `${oneLine2.slice(0, max - 1)}\u2026`;
877
+ }
863
878
  function AgentsMonitor({
864
879
  entries,
865
880
  totalCost,
@@ -867,81 +882,69 @@ function AgentsMonitor({
867
882
  nowTick
868
883
  }) {
869
884
  const all = Object.values(entries);
870
- const running = all.filter((e) => e.status === "running");
871
- const done = all.filter((e) => e.status === "success").length;
872
- const failed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
873
- const maxTools = Math.max(1, ...all.map((e) => e.toolCalls));
874
- const ordered = [...all].sort((a, b) => {
875
- const ra = a.status === "running" ? 0 : a.status === "idle" ? 1 : 2;
876
- const rb = b.status === "running" ? 0 : b.status === "idle" ? 1 : 2;
877
- if (ra !== rb) return ra - rb;
885
+ const live = all.filter((e) => !isTerminal(e.status));
886
+ const running = live.filter((e) => e.status === "running").length;
887
+ const totalDone = all.filter((e) => e.status === "success").length;
888
+ const totalFailed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
889
+ const ordered = [...live].sort((a, b) => {
890
+ if (a.status === "running" && b.status !== "running") return -1;
891
+ if (a.status !== "running" && b.status === "running") return 1;
878
892
  return a.startedAt - b.startedAt;
879
893
  });
880
- const shown = ordered.slice(0, 20);
881
- const events = [];
882
- for (const e of all) {
883
- events.push({ at: e.startedAt, icon: "\u25CF", color: "cyan", text: `${e.name} spawned` });
884
- if (e.status !== "running" && e.status !== "idle") {
885
- const s = STATUS2[e.status];
886
- events.push({
887
- at: e.lastEventAt,
888
- icon: s.icon,
889
- color: s.color,
890
- text: `${e.name} ${e.status} (${e.toolCalls}t)`
891
- });
892
- }
893
- if (e.budgetWarning) {
894
- events.push({
895
- at: e.budgetWarning.at,
896
- icon: "\u26A1",
897
- color: "yellow",
898
- text: `${e.name} ${e.budgetWarning.kind} ${e.budgetWarning.used}/${e.budgetWarning.limit} \u2014 extending`
899
- });
900
- }
901
- }
902
- events.sort((a, b) => b.at - a.at);
903
- const timeline = events.slice(0, 6);
894
+ const shown = ordered.slice(0, 8);
904
895
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
905
896
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
906
- /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "AGENTS MONITOR" }),
897
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "AGENTS \xB7 LIVE" }),
907
898
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
908
899
  /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
909
900
  "\u25B6",
910
- running.length
901
+ running
911
902
  ] }),
903
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
904
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "done" }),
912
905
  /* @__PURE__ */ jsxs(Text, { color: "green", children: [
913
906
  "\u2713",
914
- done
907
+ totalDone
915
908
  ] }),
916
- failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
909
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
910
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "failed" }),
911
+ totalFailed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
917
912
  "\u2717",
918
- failed
913
+ totalFailed
919
914
  ] }) : null,
920
915
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 Ctrl+G to close" })
921
916
  ] }),
922
917
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
923
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "agents" }),
924
- /* @__PURE__ */ jsx(Text, { color: "magenta", children: all.length }),
918
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "shown" }),
919
+ /* @__PURE__ */ jsx(Text, { color: "magenta", children: live.length }),
925
920
  totalTokens ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
921
+ " ",
926
922
  fmtTokens2(totalTokens.input),
927
923
  "\u2191 ",
928
924
  fmtTokens2(totalTokens.output),
929
925
  "\u2193"
930
926
  ] }) : null,
931
- /* @__PURE__ */ jsx(Text, { color: "green", children: ` $${totalCost.toFixed(3)}` })
927
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
928
+ "$",
929
+ totalCost.toFixed(3)
930
+ ] })
932
931
  ] }),
933
- shown.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No subagents yet \u2014 spawn with /spawn or /fleet dispatch." }) : null,
932
+ shown.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No live agents \u2014 spawn with /spawn or /fleet dispatch." }) : null,
934
933
  shown.map((e) => {
935
934
  const s = STATUS2[e.status];
936
935
  const elapsed = e.status === "running" ? fmtElapsed(Math.max(0, nowTick - e.startedAt)) : e.status;
937
936
  const spark = sparkline(bucketActivity(e.recentTools, nowTick));
938
- const tool = e.currentTool?.name ?? e.recentTools[e.recentTools.length - 1]?.name ?? "";
939
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
937
+ const lastTool = e.recentTools[e.recentTools.length - 1];
938
+ const lastMessage = e.recentMessages[e.recentMessages.length - 1];
939
+ const streamTail = e.streamingText ? snippet(e.streamingText.slice(-160)) : "";
940
+ const toolElapsed = e.currentTool ? Math.max(0, nowTick - e.currentTool.startedAt) : 0;
941
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
940
942
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
941
943
  /* @__PURE__ */ jsx(Text, { color: s.color, bold: true, children: s.icon }),
942
- /* @__PURE__ */ jsx(Text, { bold: true, children: e.name.padEnd(14).slice(0, 14) }),
943
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed.padEnd(8).slice(0, 8) }),
944
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: renderProgress(e.toolCalls / maxTools, 10) }),
944
+ /* @__PURE__ */ jsx(Text, { bold: true, children: e.name }),
945
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
946
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed }),
947
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
945
948
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
946
949
  "L",
947
950
  e.iterations,
@@ -954,24 +957,46 @@ function AgentsMonitor({
954
957
  e.extensions
955
958
  ] }) : null
956
959
  ] }),
957
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
958
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
959
- /* @__PURE__ */ jsx(Text, { color: "green", children: spark }),
960
- tool ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: tool }) : null
961
- ] })
960
+ e.status === "running" && e.currentTool ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingLeft: 2, children: [
961
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
962
+ "\u2192 ",
963
+ e.currentTool.name
964
+ ] }),
965
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
966
+ "(",
967
+ toolElapsed,
968
+ "ms)"
969
+ ] })
970
+ ] }) : null,
971
+ spark || lastTool ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingLeft: 2, children: [
972
+ /* @__PURE__ */ jsx(Text, { color: "green", children: spark || "" }),
973
+ lastTool ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
974
+ "last: ",
975
+ lastTool.name,
976
+ typeof lastTool.durationMs === "number" ? ` ${lastTool.durationMs}ms` : "",
977
+ lastTool.ok === false ? " \u2717" : ""
978
+ ] }) : null
979
+ ] }) : null,
980
+ e.status === "running" && streamTail ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
981
+ ">",
982
+ " ",
983
+ streamTail
984
+ ] }) }) : null,
985
+ (e.status !== "running" || !streamTail) && lastMessage ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
986
+ "msg: ",
987
+ snippet(lastMessage.text)
988
+ ] }) }) : null,
989
+ e.budgetWarning ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
990
+ "\u26A1 ",
991
+ e.budgetWarning.kind,
992
+ " ",
993
+ e.budgetWarning.used,
994
+ "/",
995
+ e.budgetWarning.limit,
996
+ " \u2014 extending"
997
+ ] }) }) : null
962
998
  ] }, e.id);
963
- }),
964
- timeline.length > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
965
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "timeline" }),
966
- timeline.map((ev, i) => (
967
- // biome-ignore lint/suspicious/noArrayIndexKey: timeline is rebuilt per render
968
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
969
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${fmtElapsed(Math.max(0, nowTick - ev.at))} ago`.padEnd(10) }),
970
- /* @__PURE__ */ jsx(Text, { color: ev.color, children: ev.icon }),
971
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ev.text })
972
- ] }, i)
973
- ))
974
- ] }) : null
999
+ })
975
1000
  ] });
976
1001
  }
977
1002
 
@@ -1155,7 +1180,17 @@ function padCell(text, width, align) {
1155
1180
  }
1156
1181
  function History({ entries, streamingText, toolStream }) {
1157
1182
  const { stdout } = useStdout();
1158
- const termWidth = stdout?.columns ?? 80;
1183
+ const [termSize, setTermSize] = useState({ columns: stdout?.columns ?? 80, rows: stdout?.rows ?? 24 });
1184
+ useEffect(() => {
1185
+ const handleResize = () => {
1186
+ setTermSize({ columns: stdout?.columns ?? 80, rows: stdout?.rows ?? 24 });
1187
+ };
1188
+ process.stdout.on("resize", handleResize);
1189
+ return () => {
1190
+ process.stdout.off("resize", handleResize);
1191
+ };
1192
+ }, [stdout]);
1193
+ const termWidth = termSize.columns;
1159
1194
  const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
1160
1195
  const toolTail = toolStream?.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
1161
1196
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -1954,12 +1989,12 @@ function formatMatchHit(hit) {
1954
1989
  const o = hit;
1955
1990
  const file = stringOf(o["file"]) ?? stringOf(o["path"]);
1956
1991
  const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
1957
- const snippet = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]);
1992
+ const snippet2 = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]);
1958
1993
  if (file) {
1959
1994
  const head = line !== void 0 ? `${shortenPath(file, 40)}:${line}` : shortenPath(file, 50);
1960
- return snippet ? `${head} ${truncMid(snippet.replace(/\s+/g, " "), 40)}` : head;
1995
+ return snippet2 ? `${head} ${truncMid(snippet2.replace(/\s+/g, " "), 40)}` : head;
1961
1996
  }
1962
- if (snippet) return truncMid(snippet, 70);
1997
+ if (snippet2) return truncMid(snippet2, 70);
1963
1998
  }
1964
1999
  return void 0;
1965
2000
  }
@@ -3102,6 +3137,50 @@ function reducer(state, action) {
3102
3137
  fleetTokens: action.input !== void 0 || action.output !== void 0 ? { input: action.input ?? state.fleetTokens.input, output: action.output ?? state.fleetTokens.output } : state.fleetTokens
3103
3138
  };
3104
3139
  }
3140
+ case "leaderIterStart": {
3141
+ return {
3142
+ ...state,
3143
+ leader: {
3144
+ ...state.leader,
3145
+ iterations: state.leader.iterations + 1,
3146
+ iterating: true,
3147
+ lastEventAt: Date.now()
3148
+ }
3149
+ };
3150
+ }
3151
+ case "leaderIterEnd": {
3152
+ return {
3153
+ ...state,
3154
+ leader: { ...state.leader, iterating: false, lastEventAt: Date.now() }
3155
+ };
3156
+ }
3157
+ case "leaderToolStart": {
3158
+ return {
3159
+ ...state,
3160
+ leader: {
3161
+ ...state.leader,
3162
+ currentTool: { name: action.name, startedAt: Date.now() },
3163
+ lastEventAt: Date.now()
3164
+ }
3165
+ };
3166
+ }
3167
+ case "leaderToolEnd": {
3168
+ const now = Date.now();
3169
+ const recentTools = [
3170
+ ...state.leader.recentTools,
3171
+ { name: action.name, ok: action.ok, durationMs: action.durationMs, at: now }
3172
+ ].slice(-8);
3173
+ return {
3174
+ ...state,
3175
+ leader: {
3176
+ ...state.leader,
3177
+ toolCalls: state.leader.toolCalls + 1,
3178
+ currentTool: void 0,
3179
+ recentTools,
3180
+ lastEventAt: now
3181
+ }
3182
+ };
3183
+ }
3105
3184
  case "setStreamFleet": {
3106
3185
  return { ...state, streamFleet: action.enabled };
3107
3186
  }
@@ -3299,6 +3378,15 @@ function App({
3299
3378
  confirmQueue: [],
3300
3379
  contextChipVersion: 0,
3301
3380
  fleet: {},
3381
+ leader: {
3382
+ iterations: 0,
3383
+ toolCalls: 0,
3384
+ recentTools: [],
3385
+ currentTool: void 0,
3386
+ startedAt: Date.now(),
3387
+ lastEventAt: Date.now(),
3388
+ iterating: false
3389
+ },
3302
3390
  fleetCost: 0,
3303
3391
  fleetTokens: { input: 0, output: 0 },
3304
3392
  streamFleet: true,
@@ -3396,6 +3484,25 @@ function App({
3396
3484
  }
3397
3485
  return { running, idle, pending: 0, completed };
3398
3486
  }, [state.fleet]);
3487
+ const entriesWithLeader = useMemo(() => {
3488
+ const leaderEntry = {
3489
+ id: "leader",
3490
+ name: "LEADER",
3491
+ provider,
3492
+ model,
3493
+ status: state.status === "running" || state.status === "streaming" || state.leader.iterating ? "running" : "idle",
3494
+ streamingText: "",
3495
+ iterations: state.leader.iterations,
3496
+ toolCalls: state.leader.toolCalls,
3497
+ recentTools: state.leader.recentTools,
3498
+ recentMessages: [],
3499
+ cost: 0,
3500
+ startedAt: state.leader.startedAt,
3501
+ lastEventAt: state.leader.lastEventAt,
3502
+ currentTool: state.leader.currentTool
3503
+ };
3504
+ return { leader: leaderEntry, ...state.fleet };
3505
+ }, [state.fleet, state.leader, state.status, provider, model]);
3399
3506
  const STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
3400
3507
  const labelsRef = useRef(/* @__PURE__ */ new Map());
3401
3508
  const labelFor = (id, name) => {
@@ -3411,32 +3518,6 @@ function App({
3411
3518
  m.set(id, v);
3412
3519
  return v;
3413
3520
  };
3414
- const fleetAgents = useMemo(() => {
3415
- const entries = Object.entries(state.fleet);
3416
- if (entries.length === 0) return void 0;
3417
- const active = entries.filter(([_id, e]) => e.status === "running" || e.status === "idle");
3418
- if (active.length === 0) return void 0;
3419
- active.sort((a, b) => {
3420
- const sa = a[1].status === "running" ? 0 : 1;
3421
- const sb = b[1].status === "running" ? 0 : 1;
3422
- if (sa !== sb) return sa - sb;
3423
- return a[1].startedAt - b[1].startedAt;
3424
- });
3425
- return active.slice(0, 4).map(([id, e]) => {
3426
- const lbl = labelFor(id, e.name);
3427
- return {
3428
- label: lbl.label,
3429
- color: lbl.color,
3430
- elapsedMs: Math.max(0, nowTick - e.startedAt),
3431
- toolCalls: e.toolCalls,
3432
- running: e.status === "running",
3433
- // Last/current action, so the 4th line shows what each agent is
3434
- // doing right now (e.g. "▶ 12s · 8t · bash") rather than just counts.
3435
- tool: e.currentTool?.name,
3436
- extensions: e.extensions
3437
- };
3438
- });
3439
- }, [state.fleet, nowTick]);
3440
3521
  const [planCounts, setPlanCounts] = useState(null);
3441
3522
  useEffect(() => {
3442
3523
  const planPath = agent.ctx.meta["plan.path"];
@@ -3851,6 +3932,13 @@ function App({
3851
3932
  });
3852
3933
  const offToolStart = events.on("tool.started", (e) => {
3853
3934
  dispatch({ type: "toolStarted", id: e.id, name: e.name });
3935
+ dispatch({ type: "leaderToolStart", name: e.name });
3936
+ });
3937
+ const offIterStart = events.on("iteration.started", () => {
3938
+ dispatch({ type: "leaderIterStart" });
3939
+ });
3940
+ const offIterEnd = events.on("iteration.completed", () => {
3941
+ dispatch({ type: "leaderIterEnd" });
3854
3942
  });
3855
3943
  const offToolProgress = events.on("tool.progress", (e) => {
3856
3944
  if (e.event.type !== "partial_output" || !e.event.text) return;
@@ -3882,6 +3970,7 @@ function App({
3882
3970
  });
3883
3971
  dispatch({ type: "toolEnded", name: e.name });
3884
3972
  dispatch({ type: "toolStreamClear", name: e.name });
3973
+ dispatch({ type: "leaderToolEnd", name: e.name, ok: e.ok, durationMs: e.durationMs });
3885
3974
  if (e.ok && e.name === "todo") {
3886
3975
  dispatch({
3887
3976
  type: "addEntry",
@@ -3941,6 +4030,8 @@ function App({
3941
4030
  return () => {
3942
4031
  offDelta();
3943
4032
  offToolStart();
4033
+ offIterStart();
4034
+ offIterEnd();
3944
4035
  offToolProgress();
3945
4036
  offTool();
3946
4037
  offRetry();
@@ -4112,16 +4203,26 @@ function App({
4112
4203
  }, [events, onClearHistory]);
4113
4204
  useEffect(() => {
4114
4205
  const offFired = events.on("compaction.fired", (e) => {
4115
- const { level, tokens, load, maxContext: maxContext2, report, aggressive } = e;
4206
+ const { level, tokens, load, maxContext: maxContext2, report } = e;
4116
4207
  const pct = (load * 100).toFixed(0);
4117
4208
  const before = report.before;
4118
4209
  const after = report.after;
4119
4210
  const saved = before - after;
4211
+ if (saved <= 0) {
4212
+ dispatch({
4213
+ type: "addEntry",
4214
+ entry: {
4215
+ kind: "info",
4216
+ text: `\u25B8 compaction skipped at ${level} \u2014 load ${pct}% (${tokens.toLocaleString()} of ${maxContext2.toLocaleString()} tok). preserveK protects recent turns; nothing to elide.`
4217
+ }
4218
+ });
4219
+ return;
4220
+ }
4120
4221
  const table = [
4121
- `\u25B8 context compacted at ${level} (${pct}% of ${maxContext2.toLocaleString()} tok)`,
4122
- ` tokens before ${before.toLocaleString().padStart(8)}`,
4123
- ` tokens after ${after.toLocaleString().padStart(8)}`,
4124
- ` saved ${saved.toLocaleString().padStart(8)} (${(saved / before * 100).toFixed(1)}%)`
4222
+ `\u25B8 context compacted at ${level} \u2014 load ${pct}% (${tokens.toLocaleString()} of ${maxContext2.toLocaleString()} tok, full request)`,
4223
+ ` msg tokens before ${before.toLocaleString().padStart(8)}`,
4224
+ ` msg tokens after ${after.toLocaleString().padStart(8)}`,
4225
+ ` saved ${saved.toLocaleString().padStart(8)} (${(saved / before * 100).toFixed(1)}%)`
4125
4226
  ];
4126
4227
  for (const line of table) {
4127
4228
  dispatch({ type: "addEntry", entry: { kind: "info", text: line } });
@@ -4130,7 +4231,7 @@ function App({
4130
4231
  const offFailed = events.on("compaction.failed", (e) => {
4131
4232
  const { level, load, maxContext: maxContext2, fatal } = e;
4132
4233
  const pct = (load * 100).toFixed(0);
4133
- const text = fatal ? `\u2717 compaction failed at ${level} (${pct}% of ${maxContext2.toLocaleString()} tok) \u2014 FATAL` : `\u26A0 compaction failed at ${level} (${pct}% of ${maxContext2.toLocaleString()} tok) \u2014 continuing`;
4234
+ const text = fatal ? `\u2717 compaction failed at ${level} \u2014 load ${pct}% of ${maxContext2.toLocaleString()} tok \u2014 FATAL` : `\u26A0 compaction failed at ${level} \u2014 load ${pct}% of ${maxContext2.toLocaleString()} tok \u2014 continuing`;
4134
4235
  dispatch({ type: "addEntry", entry: { kind: fatal ? "error" : "warn", text } });
4135
4236
  });
4136
4237
  return () => {
@@ -5314,7 +5415,6 @@ User message:
5314
5415
  todos,
5315
5416
  plan: planCounts ?? void 0,
5316
5417
  fleet: fleetCounts,
5317
- fleetAgents,
5318
5418
  git: gitInfo,
5319
5419
  context: contextWindow,
5320
5420
  projectName,
@@ -5328,13 +5428,12 @@ User message:
5328
5428
  state.agentsMonitorOpen ? /* @__PURE__ */ jsx(
5329
5429
  AgentsMonitor,
5330
5430
  {
5331
- entries: state.fleet,
5431
+ entries: entriesWithLeader,
5332
5432
  totalCost: state.fleetCost,
5333
5433
  totalTokens: state.fleetTokens,
5334
5434
  nowTick
5335
5435
  }
5336
- ) : null,
5337
- state.monitorOpen ? /* @__PURE__ */ jsx(
5436
+ ) : state.monitorOpen ? /* @__PURE__ */ jsx(
5338
5437
  FleetMonitor,
5339
5438
  {
5340
5439
  entries: state.fleet,