@wrongstack/tui 0.250.0 → 0.256.0

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
@@ -1,6 +1,7 @@
1
- import { writeErr, resolveWstackPaths, loadGoal, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, wstackGlobalRoot, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, buildChildEnv } from '@wrongstack/core';
1
+ import { writeErr, resolveProjectDir, wstackGlobalRoot, GlobalMailbox, resolveWstackPaths, loadGoal, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, buildChildEnv } from '@wrongstack/core';
2
2
  export { buildGoalPreamble } from '@wrongstack/core';
3
3
  import { Box as Box$1, useInput, useStdin, useStdout, Text as Text$1, render, useApp, measureElement, Static } from 'ink';
4
+ import { randomUUID } from 'crypto';
4
5
  import * as path4 from 'path';
5
6
  import React5, { forwardRef, useState, useEffect, useMemo, memo, useRef, useCallback, useReducer, useLayoutEffect } from 'react';
6
7
  import * as fs2 from 'fs/promises';
@@ -86,6 +87,27 @@ var Box = forwardRef(function Box2({ borderColor, backgroundColor, ...rest }, re
86
87
  }
87
88
  );
88
89
  });
90
+ function useTokenCounterRefresh(tokenCounter, events) {
91
+ const [data, setData] = useState(
92
+ () => tokenCounter ? {
93
+ usage: tokenCounter.total(),
94
+ cost: tokenCounter.estimateCost(),
95
+ cacheStats: tokenCounter.cacheStats()
96
+ } : void 0
97
+ );
98
+ useEffect(() => {
99
+ if (!tokenCounter || !events) return;
100
+ const off = events.on("token.accounted", () => {
101
+ setData({
102
+ usage: tokenCounter.total(),
103
+ cost: tokenCounter.estimateCost(),
104
+ cacheStats: tokenCounter.cacheStats()
105
+ });
106
+ });
107
+ return off;
108
+ }, [tokenCounter, events]);
109
+ return data;
110
+ }
89
111
  var MODE_ICONS = {
90
112
  teach: "\u{1F9D1}\u200D\u{1F3EB}",
91
113
  brief: "\u26A1",
@@ -131,6 +153,7 @@ function StatusBar({
131
153
  processCount,
132
154
  context,
133
155
  hiddenItems,
156
+ events,
134
157
  eternalStage,
135
158
  goalSummary,
136
159
  indexState,
@@ -140,7 +163,9 @@ function StatusBar({
140
163
  nextStepsAutoSubmitCountdown,
141
164
  autoProceedCountdown,
142
165
  sessionCount,
143
- mailbox
166
+ mailbox,
167
+ tokenSavingMode,
168
+ toolCount
144
169
  }) {
145
170
  const { stdout } = useStdout();
146
171
  const [termWidth, setTermWidth] = useState(stdout?.columns ?? 90);
@@ -155,9 +180,10 @@ function StatusBar({
155
180
  const isCompact = termWidth < COMPACT_THRESHOLD;
156
181
  const isComfortable = termWidth >= COMFORTABLE_THRESHOLD;
157
182
  const hiddenSet = new Set(hiddenItems);
158
- const usage = tokenCounter?.total();
159
- const cost = tokenCounter?.estimateCost();
160
- const cache2 = tokenCounter?.cacheStats();
183
+ const tokenData = useTokenCounterRefresh(tokenCounter, events);
184
+ const usage = tokenData?.usage;
185
+ const cost = tokenData?.cost;
186
+ const cache2 = tokenData?.cacheStats;
161
187
  const [elapsedMs, setElapsedMs] = useState(startedAt ? Date.now() - startedAt : 0);
162
188
  useEffect(() => {
163
189
  if (startedAt == null) return;
@@ -178,7 +204,7 @@ function StatusBar({
178
204
  const statePrefix = state === "idle" || state === "aborting" ? "\u25CF" : spinner;
179
205
  const thinking = state === "running" || state === "streaming";
180
206
  const hasAutoProceed = autoProceedCountdown != null && autoProceedCountdown > 0;
181
- const hasSecondLine = yolo || autonomy && autonomy !== "off" || startedAt != null || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0 || workingDir !== void 0 && workingDir.length > 0 || goalSummary !== null && goalSummary !== void 0 || !!modeLabel || hasAutoProceed;
207
+ const hasSecondLine = yolo || autonomy && autonomy !== "off" || startedAt != null || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0 || workingDir !== void 0 && workingDir.length > 0 || goalSummary !== null && goalSummary !== void 0 || !!modeLabel || hasAutoProceed || tokenSavingMode || typeof toolCount === "number" && toolCount > 0;
182
208
  const fleetHasActivity = fleet && (fleet.running > 0 || fleet.idle > 0 || fleet.pending > 0 || fleet.completed > 0) || subagentCount > 0;
183
209
  const hasBrainActivity = !!brain && brain.state !== "idle";
184
210
  const hasDebugStream = !!debugStreamStats;
@@ -399,6 +425,19 @@ function StatusBar({
399
425
  " session",
400
426
  sessionCount === 1 ? "" : "s"
401
427
  ] })
428
+ ] }) : null,
429
+ toolCount != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
430
+ yolo || startedAt != null || projectName || workingDir || git || sessionCount ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
431
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
432
+ "\u{1F527} ",
433
+ toolCount,
434
+ " tool",
435
+ toolCount === 1 ? "" : "s"
436
+ ] })
437
+ ] }) : null,
438
+ tokenSavingMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
439
+ yolo || startedAt != null || projectName || workingDir || git || sessionCount || toolCount ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
440
+ /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "\u{1F4BE} save" })
402
441
  ] }) : null
403
442
  ] }) : /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
404
443
  hasThirdLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
@@ -555,7 +594,22 @@ function StatusBar({
555
594
  /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
556
595
  "\u{1F465} ",
557
596
  mailbox.onlineAgents,
558
- " online"
597
+ " agent",
598
+ mailbox.onlineAgents === 1 ? "" : "s",
599
+ mailbox.onlineClients.tui + mailbox.onlineClients.webui + mailbox.onlineClients.repl > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
600
+ mailbox.onlineClients.tui > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
601
+ " \xB7 \u{1F5A5} TUI",
602
+ mailbox.onlineClients.tui > 1 ? `\xD7${mailbox.onlineClients.tui}` : ""
603
+ ] }) : null,
604
+ mailbox.onlineClients.webui > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
605
+ " \xB7 \u{1F310} WebUI",
606
+ mailbox.onlineClients.webui > 1 ? `\xD7${mailbox.onlineClients.webui}` : ""
607
+ ] }) : null,
608
+ mailbox.onlineClients.repl > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
609
+ " \xB7 \u2328 REPL",
610
+ mailbox.onlineClients.repl > 1 ? `\xD7${mailbox.onlineClients.repl}` : ""
611
+ ] }) : null
612
+ ] }) : null
559
613
  ] }),
560
614
  mailbox.lastSubject ? /* @__PURE__ */ jsxs(Fragment, { children: [
561
615
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
@@ -2807,7 +2861,7 @@ function MarkdownView({
2807
2861
  const tableEnd = detectTable(lines, i);
2808
2862
  if (tableEnd > i) {
2809
2863
  rows.push(
2810
- /* @__PURE__ */ jsx(Text, { children: renderTable(lines.slice(i, tableEnd), tableBudget) }, `t${key++}`)
2864
+ /* @__PURE__ */ jsx(Box, { width: tableBudget, backgroundColor: "transparent", children: /* @__PURE__ */ jsx(Text, { children: renderTable(lines.slice(i, tableEnd), tableBudget) }) }, `t${key++}`)
2811
2865
  );
2812
2866
  i = tableEnd;
2813
2867
  continue;
@@ -2827,10 +2881,14 @@ function MarkdownView({
2827
2881
  rows.push(
2828
2882
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2829
2883
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
2830
- /[\u2500-\u257F]/.test(qContent) ? /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: [...qContent].slice(0, (contentWidth ?? termWidth) - 2).map((ch, ci) => (
2831
- /* biome-ignore lint/suspicious/noArrayIndexKey: characters are not reorderable */
2832
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ch }, ci)
2833
- )) }) : /* @__PURE__ */ jsx(InlineLine, { tokens: parseInline(qContent), dim: true })
2884
+ /[\u2500-\u257F]/.test(qContent) ? (
2885
+ // Box-drawing characters inside blockquotes also need transparent
2886
+ // background to avoid inheriting the message panel background.
2887
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", backgroundColor: "transparent", children: [...qContent].slice(0, (contentWidth ?? termWidth) - 2).map((ch, ci) => (
2888
+ /* biome-ignore lint/suspicious/noArrayIndexKey: characters are not reorderable */
2889
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ch }, ci)
2890
+ )) })
2891
+ ) : /* @__PURE__ */ jsx(InlineLine, { tokens: parseInline(qContent), dim: true })
2834
2892
  ] }, `q${key++}`)
2835
2893
  );
2836
2894
  continue;
@@ -2859,7 +2917,7 @@ function MarkdownView({
2859
2917
  const maxW = contentWidth ?? termWidth;
2860
2918
  const chars = [...line].slice(0, maxW);
2861
2919
  rows.push(
2862
- /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: chars.map((ch, ci) => (
2920
+ /* @__PURE__ */ jsx(Box, { width: maxW, backgroundColor: "transparent", flexDirection: "row", children: chars.map((ch, ci) => (
2863
2921
  /* biome-ignore lint/suspicious/noArrayIndexKey: characters are not reorderable */
2864
2922
  /* @__PURE__ */ jsx(Text, { children: ch }, ci)
2865
2923
  )) }, `bx${key++}`)
@@ -3761,11 +3819,12 @@ function Banner({
3761
3819
  }
3762
3820
 
3763
3821
  // src/components/suggestions.ts
3764
- var STRICT_HEADING_RE = /💡\s*Next steps?\s*\n+/i;
3822
+ var STRICT_HEADING_RE = /(?:💡\s*Next steps?|<next_steps>)\s*\n+/i;
3765
3823
  var PERMISSIVE_HEADING_PATTERNS = [
3766
3824
  { re: /💡\s*Next steps?\s*\n+/i, label: "emoji" },
3767
3825
  { re: /##?\s*Next steps?\s*\n+/i, label: "markdown" },
3768
- { re: /\n{1,2}Next steps?\s*\n+/i, label: "plain" }
3826
+ { re: /\n{1,2}Next steps?\s*\n+/i, label: "plain" },
3827
+ { re: /<next_steps>\s*\n+/i, label: "xml-tag" }
3769
3828
  ];
3770
3829
  var ITEM_RE = /^(?:(\d+)[.)]\s*|[-*•]\s*)(.+)$/;
3771
3830
  var MAX_STEPS = 6;
@@ -3849,6 +3908,10 @@ function findBlockEnd(afterHeading, stepCount) {
3849
3908
  let found = 0;
3850
3909
  for (const rawLine of lines) {
3851
3910
  const line = rawLine.trim();
3911
+ if (line === "</next_steps>") {
3912
+ consumed += rawLine.length + 1;
3913
+ break;
3914
+ }
3852
3915
  if (!line) {
3853
3916
  consumed += rawLine.length + 1;
3854
3917
  continue;
@@ -3913,6 +3976,7 @@ var Entry = React5.memo(function Entry2({
3913
3976
  borderRight: false,
3914
3977
  borderBottom: false,
3915
3978
  borderColor: theme.user,
3979
+ backgroundColor: "#1e1e2e",
3916
3980
  paddingLeft: 1,
3917
3981
  children: /* @__PURE__ */ jsxs(Text, { children: [
3918
3982
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.user, children: "USER " }),
@@ -3942,8 +4006,9 @@ var Entry = React5.memo(function Entry2({
3942
4006
  borderStyle: "single",
3943
4007
  borderTop: false,
3944
4008
  borderRight: false,
3945
- borderBottom: hasNext ? false : void 0,
4009
+ borderBottom: false,
3946
4010
  borderColor: theme.assistant,
4011
+ backgroundColor: "#1e1e2e",
3947
4012
  paddingLeft: 1,
3948
4013
  children: [
3949
4014
  /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }) }),
@@ -4308,6 +4373,15 @@ function startHeapWatchdog(opts = {}) {
4308
4373
  }).catch(() => void 0);
4309
4374
  };
4310
4375
  const tick = () => {
4376
+ let userTimings = 0;
4377
+ try {
4378
+ userTimings = performance.getEntriesByType("mark").length + performance.getEntriesByType("measure").length;
4379
+ if (userTimings > 0) {
4380
+ performance.clearMarks();
4381
+ performance.clearMeasures();
4382
+ }
4383
+ } catch {
4384
+ }
4311
4385
  const s2 = takeHeapSample();
4312
4386
  if (s2.load >= criticalAt && criticalArmed) {
4313
4387
  criticalArmed = false;
@@ -4336,7 +4410,7 @@ function startHeapWatchdog(opts = {}) {
4336
4410
  extras = opts.collectStats?.() ?? {};
4337
4411
  } catch {
4338
4412
  }
4339
- append(JSON.stringify({ pid: process.pid, ...s2, ...extras }));
4413
+ append(JSON.stringify({ pid: process.pid, ...s2, userTimings, ...extras }));
4340
4414
  }
4341
4415
  };
4342
4416
  const timer = setInterval(tick, sampleEveryMs);
@@ -5495,7 +5569,7 @@ var MODE_DESC = {
5495
5569
  suggest: "Shows next-step suggestions after each turn",
5496
5570
  auto: "Self-driving \u2014 agent continues automatically"
5497
5571
  };
5498
- var SETTINGS_FIELD_COUNT = 25;
5572
+ var SETTINGS_FIELD_COUNT = 26;
5499
5573
  var CONFIG_SCOPES = ["global", "project"];
5500
5574
  function SettingsPicker({
5501
5575
  field,
@@ -5512,6 +5586,7 @@ function SettingsPicker({
5512
5586
  featureMemory,
5513
5587
  featureSkills,
5514
5588
  featureModelsRegistry,
5589
+ featureTokenSaving,
5515
5590
  contextAutoCompact,
5516
5591
  contextStrategy,
5517
5592
  logLevel,
@@ -5595,6 +5670,11 @@ function SettingsPicker({
5595
5670
  value: boolVal(featureModelsRegistry),
5596
5671
  detail: "Fetch models.dev catalog at startup"
5597
5672
  },
5673
+ {
5674
+ label: "Token-saving mode",
5675
+ value: boolVal(featureTokenSaving),
5676
+ detail: "Omit non-essential tools and trim system prompt to save tokens"
5677
+ },
5598
5678
  // ── Context ──
5599
5679
  { section: "Context" },
5600
5680
  {
@@ -6695,6 +6775,36 @@ function handleCollabDone(event, dispatch, stateRef) {
6695
6775
  verdict: payload.report.overallVerdict ?? "needs_revision"
6696
6776
  });
6697
6777
  }
6778
+ function useStatuslineState(opts) {
6779
+ const [liveModel, setLiveModel] = useState(opts.model);
6780
+ const [liveProvider, setLiveProvider] = useState(opts.provider ?? "agent");
6781
+ const [activeMaxContext, setActiveMaxContext] = useState(opts.effectiveMaxContext);
6782
+ const [yoloLive, setYoloLive] = useState(opts.yolo);
6783
+ const [autonomyLive, setAutonomyLive] = useState(opts.getAutonomy?.() ?? "off");
6784
+ const [liveModeLabel, setLiveModeLabel] = useState(opts.modeLabel ?? "");
6785
+ const [hiddenItems, setHiddenItems] = useState(
6786
+ Array.isArray(opts.statuslineHiddenItems) ? [...opts.statuslineHiddenItems] : [...opts.statuslineHiddenItems]
6787
+ );
6788
+ const [sessionCount, setSessionCount] = useState(0);
6789
+ return {
6790
+ liveModel,
6791
+ setLiveModel,
6792
+ liveProvider,
6793
+ setLiveProvider,
6794
+ activeMaxContext,
6795
+ setActiveMaxContext,
6796
+ yoloLive,
6797
+ setYoloLive,
6798
+ autonomyLive,
6799
+ setAutonomyLive,
6800
+ liveModeLabel,
6801
+ setLiveModeLabel,
6802
+ hiddenItems,
6803
+ setHiddenItems,
6804
+ sessionCount,
6805
+ setSessionCount
6806
+ };
6807
+ }
6698
6808
  function useTuiControllers({
6699
6809
  dispatch,
6700
6810
  streamFleet,
@@ -6931,6 +7041,7 @@ function useSessionEvents(events, dispatch, onClearHistory) {
6931
7041
  const offRewound = events.on("session.rewound", () => {
6932
7042
  dispatch({ type: "sessionRewound", toPromptIndex: 0 });
6933
7043
  dispatch({ type: "clearHistory" });
7044
+ dispatch({ type: "resetContextChip" });
6934
7045
  onClearHistory?.(dispatch);
6935
7046
  });
6936
7047
  return () => {
@@ -7760,6 +7871,7 @@ function reducer(state, action) {
7760
7871
  featureMemory: action.featureMemory,
7761
7872
  featureSkills: action.featureSkills,
7762
7873
  featureModelsRegistry: action.featureModelsRegistry,
7874
+ featureTokenSaving: action.featureTokenSaving,
7763
7875
  contextAutoCompact: action.contextAutoCompact,
7764
7876
  contextStrategy: action.contextStrategy,
7765
7877
  logLevel: action.logLevel,
@@ -7817,53 +7929,54 @@ function reducer(state, action) {
7817
7929
  if (f === 10) return { ...state, settingsPicker: { ...sp, featureMemory: !sp.featureMemory, hint: void 0 } };
7818
7930
  if (f === 11) return { ...state, settingsPicker: { ...sp, featureSkills: !sp.featureSkills, hint: void 0 } };
7819
7931
  if (f === 12) return { ...state, settingsPicker: { ...sp, featureModelsRegistry: !sp.featureModelsRegistry, hint: void 0 } };
7820
- if (f === 13) return { ...state, settingsPicker: { ...sp, contextAutoCompact: !sp.contextAutoCompact, hint: void 0 } };
7821
- if (f === 14) {
7932
+ if (f === 13) return { ...state, settingsPicker: { ...sp, featureTokenSaving: !sp.featureTokenSaving, hint: void 0 } };
7933
+ if (f === 14) return { ...state, settingsPicker: { ...sp, contextAutoCompact: !sp.contextAutoCompact, hint: void 0 } };
7934
+ if (f === 15) {
7822
7935
  const i = COMPACTOR_STRATEGIES.indexOf(sp.contextStrategy);
7823
7936
  const base = i < 0 ? 0 : i;
7824
7937
  const next = (base + action.delta + COMPACTOR_STRATEGIES.length) % COMPACTOR_STRATEGIES.length;
7825
7938
  return { ...state, settingsPicker: { ...sp, contextStrategy: expectDefined$1(COMPACTOR_STRATEGIES[next]), hint: void 0 } };
7826
7939
  }
7827
- if (f === 15) {
7940
+ if (f === 16) {
7828
7941
  const i = LOG_LEVELS.indexOf(sp.logLevel);
7829
7942
  const base = i < 0 ? 0 : i;
7830
7943
  const next = (base + action.delta + LOG_LEVELS.length) % LOG_LEVELS.length;
7831
7944
  return { ...state, settingsPicker: { ...sp, logLevel: expectDefined$1(LOG_LEVELS[next]), hint: void 0 } };
7832
7945
  }
7833
- if (f === 16) {
7946
+ if (f === 17) {
7834
7947
  const i = AUDIT_LEVELS.indexOf(sp.auditLevel);
7835
7948
  const base = i < 0 ? 0 : i;
7836
7949
  const next = (base + action.delta + AUDIT_LEVELS.length) % AUDIT_LEVELS.length;
7837
7950
  return { ...state, settingsPicker: { ...sp, auditLevel: expectDefined$1(AUDIT_LEVELS[next]), hint: void 0 } };
7838
7951
  }
7839
- if (f === 17) return { ...state, settingsPicker: { ...sp, indexOnStart: !sp.indexOnStart, hint: void 0 } };
7840
- if (f === 18) {
7952
+ if (f === 18) return { ...state, settingsPicker: { ...sp, indexOnStart: !sp.indexOnStart, hint: void 0 } };
7953
+ if (f === 19) {
7841
7954
  const j = MAX_ITERATIONS_PRESETS.indexOf(sp.maxIterations);
7842
7955
  const base = j < 0 ? 0 : j;
7843
7956
  const next = (base + action.delta + MAX_ITERATIONS_PRESETS.length) % MAX_ITERATIONS_PRESETS.length;
7844
7957
  return { ...state, settingsPicker: { ...sp, maxIterations: expectDefined$1(MAX_ITERATIONS_PRESETS[next]), hint: void 0 } };
7845
7958
  }
7846
- if (f === 19) {
7959
+ if (f === 20) {
7847
7960
  const aj = AUTO_PROCEED_MAX_PRESETS.indexOf(sp.autoProceedMaxIterations);
7848
7961
  const abase = aj < 0 ? 0 : aj;
7849
7962
  const anext = (abase + action.delta + AUTO_PROCEED_MAX_PRESETS.length) % AUTO_PROCEED_MAX_PRESETS.length;
7850
7963
  return { ...state, settingsPicker: { ...sp, autoProceedMaxIterations: expectDefined$1(AUTO_PROCEED_MAX_PRESETS[anext]), hint: void 0 } };
7851
7964
  }
7852
- if (f === 20) {
7965
+ if (f === 21) {
7853
7966
  const ej = ENHANCE_DELAY_PRESETS.indexOf(sp.enhanceDelayMs);
7854
7967
  const ebase = ej < 0 ? 0 : ej;
7855
7968
  const enext = (ebase + action.delta + ENHANCE_DELAY_PRESETS.length) % ENHANCE_DELAY_PRESETS.length;
7856
7969
  return { ...state, settingsPicker: { ...sp, enhanceDelayMs: expectDefined$1(ENHANCE_DELAY_PRESETS[enext]), hint: void 0 } };
7857
7970
  }
7858
- if (f === 21) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
7859
- if (f === 22) {
7971
+ if (f === 22) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
7972
+ if (f === 23) {
7860
7973
  const i = ENHANCE_LANGUAGES.indexOf(sp.enhanceLanguage);
7861
7974
  const base = i < 0 ? 0 : i;
7862
7975
  const next = (base + action.delta + ENHANCE_LANGUAGES.length) % ENHANCE_LANGUAGES.length;
7863
7976
  return { ...state, settingsPicker: { ...sp, enhanceLanguage: expectDefined$1(ENHANCE_LANGUAGES[next]), hint: void 0 } };
7864
7977
  }
7865
- if (f === 23) return { ...state, settingsPicker: { ...sp, debugStream: !sp.debugStream, hint: void 0 } };
7866
- if (f === 24) {
7978
+ if (f === 24) return { ...state, settingsPicker: { ...sp, debugStream: !sp.debugStream, hint: void 0 } };
7979
+ if (f === 25) {
7867
7980
  const i = CONFIG_SCOPES.indexOf(sp.configScope);
7868
7981
  const base = i < 0 ? 0 : i;
7869
7982
  const next = (base + action.delta + CONFIG_SCOPES.length) % CONFIG_SCOPES.length;
@@ -8702,6 +8815,8 @@ function App({
8702
8815
  provider,
8703
8816
  family,
8704
8817
  keyTail,
8818
+ tokenSavingMode,
8819
+ toolCount,
8705
8820
  getPickableProviders,
8706
8821
  switchProviderAndModel,
8707
8822
  getSettings,
@@ -8719,6 +8834,7 @@ function App({
8719
8834
  listSessions,
8720
8835
  onResumeSession,
8721
8836
  fleetStreamController,
8837
+ interruptController,
8722
8838
  statuslineHiddenItems,
8723
8839
  setStatuslineHiddenItems,
8724
8840
  agentsMonitorController,
@@ -8739,14 +8855,32 @@ function App({
8739
8855
  }) {
8740
8856
  const { exit } = useApp();
8741
8857
  const { stdout } = useStdout();
8742
- const [liveModel, setLiveModel] = useState(model);
8743
- const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
8744
- const [activeMaxContext, setActiveMaxContext] = useState(effectiveMaxContext);
8745
- const [yoloLive, setYoloLive] = useState(yolo);
8746
- const [autonomyLive, setAutonomyLive] = useState(getAutonomy?.() ?? "off");
8747
- const [liveModeLabel, setLiveModeLabel] = useState(modeLabel ?? "");
8748
- const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
8749
- const [sessionCount, setSessionCount] = useState(0);
8858
+ const {
8859
+ liveModel,
8860
+ setLiveModel,
8861
+ liveProvider,
8862
+ setLiveProvider,
8863
+ activeMaxContext,
8864
+ setActiveMaxContext,
8865
+ yoloLive,
8866
+ setYoloLive,
8867
+ autonomyLive,
8868
+ setAutonomyLive,
8869
+ liveModeLabel,
8870
+ setLiveModeLabel,
8871
+ hiddenItems,
8872
+ setHiddenItems,
8873
+ sessionCount,
8874
+ setSessionCount
8875
+ } = useStatuslineState({
8876
+ model,
8877
+ provider,
8878
+ effectiveMaxContext,
8879
+ yolo,
8880
+ getAutonomy,
8881
+ modeLabel,
8882
+ statuslineHiddenItems
8883
+ });
8750
8884
  const prevBranchRef = useRef(null);
8751
8885
  const [indexState, setIndexState] = useState(() => getIndexState());
8752
8886
  useEffect(() => {
@@ -8754,7 +8888,7 @@ function App({
8754
8888
  return onIndexStateChange((next) => setIndexState(next));
8755
8889
  }, []);
8756
8890
  useEffect(() => {
8757
- setHiddenItems(statuslineHiddenItems);
8891
+ setHiddenItems([...statuslineHiddenItems]);
8758
8892
  }, [statuslineHiddenItems]);
8759
8893
  useEffect(() => {
8760
8894
  setStatuslineHiddenItems(hiddenItems);
@@ -8850,7 +8984,7 @@ function App({
8850
8984
  },
8851
8985
  autonomyPicker: { open: false, options: [], selected: 0 },
8852
8986
  resumePicker: { open: false, sessions: [], selected: 0, busy: false, hint: void 0, error: void 0 },
8853
- settingsPicker: { open: false, field: 0, mode: "off", delayMs: 0, titleAnimation: true, yolo: false, streamFleet: true, chime: false, confirmExit: true, nextPrediction: false, featureMcp: true, featurePlugins: true, featureMemory: true, featureSkills: true, featureModelsRegistry: true, contextAutoCompact: true, contextStrategy: "hybrid", logLevel: "info", auditLevel: "standard", indexOnStart: true, maxIterations: 500, autoProceedMaxIterations: 50, enhanceDelayMs: 6e4, enhanceEnabled: true, enhanceLanguage: "original", debugStream: false, configScope: "global" },
8987
+ settingsPicker: { open: false, field: 0, mode: "off", delayMs: 0, titleAnimation: true, yolo: false, streamFleet: true, chime: false, confirmExit: true, nextPrediction: false, featureMcp: true, featurePlugins: true, featureMemory: true, featureSkills: true, featureModelsRegistry: true, featureTokenSaving: false, contextAutoCompact: true, contextStrategy: "hybrid", logLevel: "info", auditLevel: "standard", indexOnStart: true, maxIterations: 500, autoProceedMaxIterations: 50, enhanceDelayMs: 6e4, enhanceEnabled: true, enhanceLanguage: "original", debugStream: false, configScope: "global" },
8854
8988
  projectPicker: { open: false, allItems: [], items: [], selected: 0, filter: "", hint: void 0 },
8855
8989
  confirmQueue: [],
8856
8990
  enhance: null,
@@ -9148,7 +9282,8 @@ function App({
9148
9282
  }, [getLiveSessions]);
9149
9283
  const [mailboxStatus, setMailboxStatus] = useState({
9150
9284
  unread: 0,
9151
- onlineAgents: 0
9285
+ onlineAgents: 0,
9286
+ onlineClients: { tui: 0, webui: 0, repl: 0 }
9152
9287
  });
9153
9288
  useEffect(() => {
9154
9289
  const seenAgents = /* @__PURE__ */ new Set();
@@ -9174,11 +9309,25 @@ function App({
9174
9309
  if (p?.agentId) seenAgents.add(p.agentId);
9175
9310
  setMailboxStatus((prev) => ({ ...prev, onlineAgents: seenAgents.size }));
9176
9311
  });
9312
+ const unsub5 = events.onPattern("mailbox.sync_clients", (_e, payload) => {
9313
+ const p = payload;
9314
+ if (p) {
9315
+ setMailboxStatus((prev) => ({
9316
+ ...prev,
9317
+ onlineClients: {
9318
+ tui: p.tui ?? 0,
9319
+ webui: p.webui ?? 0,
9320
+ repl: p.repl ?? 0
9321
+ }
9322
+ }));
9323
+ }
9324
+ });
9177
9325
  return () => {
9178
9326
  unsub1();
9179
9327
  unsub2();
9180
9328
  unsub3();
9181
9329
  unsub4();
9330
+ unsub5();
9182
9331
  };
9183
9332
  }, [events]);
9184
9333
  const [mailboxPanelOpen, setMailboxPanelOpen] = useState(false);
@@ -9467,6 +9616,7 @@ function App({
9467
9616
  featureMemory: sp.featureMemory,
9468
9617
  featureSkills: sp.featureSkills,
9469
9618
  featureModelsRegistry: sp.featureModelsRegistry,
9619
+ featureTokenSaving: sp.featureTokenSaving,
9470
9620
  contextAutoCompact: sp.contextAutoCompact,
9471
9621
  contextStrategy: sp.contextStrategy,
9472
9622
  logLevel: sp.logLevel,
@@ -9844,6 +9994,7 @@ function App({
9844
9994
  featureMemory: s2.featureMemory ?? true,
9845
9995
  featureSkills: s2.featureSkills ?? true,
9846
9996
  featureModelsRegistry: s2.featureModelsRegistry ?? true,
9997
+ featureTokenSaving: s2.featureTokenSaving ?? false,
9847
9998
  contextAutoCompact: s2.contextAutoCompact ?? true,
9848
9999
  contextStrategy: s2.contextStrategy ?? "hybrid",
9849
10000
  logLevel: s2.logLevel ?? "info",
@@ -9961,6 +10112,7 @@ function App({
9961
10112
  featureMemory: sp.featureMemory,
9962
10113
  featureSkills: sp.featureSkills,
9963
10114
  featureModelsRegistry: sp.featureModelsRegistry,
10115
+ featureTokenSaving: sp.featureTokenSaving,
9964
10116
  contextAutoCompact: sp.contextAutoCompact,
9965
10117
  contextStrategy: sp.contextStrategy,
9966
10118
  logLevel: sp.logLevel,
@@ -10170,6 +10322,15 @@ function App({
10170
10322
  entry: { kind: "error", text: e.description }
10171
10323
  });
10172
10324
  });
10325
+ const offFallback = events.on("provider.fallback", (e) => {
10326
+ dispatch({
10327
+ type: "addEntry",
10328
+ entry: {
10329
+ kind: "warn",
10330
+ text: `\u21BB rate-limited (${e.status}) \u2014 switched to ${e.to.providerId}/${e.to.model}`
10331
+ }
10332
+ });
10333
+ });
10173
10334
  const offProvResp = events.on("provider.response", () => {
10174
10335
  const text = streamingTextRef.current;
10175
10336
  streamingTextRef.current = "";
@@ -10243,6 +10404,7 @@ function App({
10243
10404
  offTool();
10244
10405
  offRetry();
10245
10406
  offProvErr();
10407
+ offFallback();
10246
10408
  offProvResp();
10247
10409
  offConfirmNeeded();
10248
10410
  offTrustPersisted();
@@ -10304,6 +10466,15 @@ function App({
10304
10466
  enhanceController,
10305
10467
  agentsMonitorController
10306
10468
  });
10469
+ useEffect(() => {
10470
+ if (!interruptController) return;
10471
+ interruptController.abortLeader = () => {
10472
+ if (stateRef.current.status === "idle") return false;
10473
+ activeCtrlRef.current?.abort("user interrupt (/interrupt)");
10474
+ dispatch({ type: "status", status: "aborting" });
10475
+ return true;
10476
+ };
10477
+ }, [interruptController, dispatch, stateRef]);
10307
10478
  const lastEscAtRef = useRef(0);
10308
10479
  const ESC_DOUBLE_PRESS_MS = 1e3;
10309
10480
  useDirectorFleetBridge({
@@ -11159,6 +11330,7 @@ function App({
11159
11330
  featureMemory: cfg.featureMemory ?? true,
11160
11331
  featureSkills: cfg.featureSkills ?? true,
11161
11332
  featureModelsRegistry: cfg.featureModelsRegistry ?? true,
11333
+ featureTokenSaving: cfg.featureTokenSaving ?? false,
11162
11334
  contextAutoCompact: cfg.contextAutoCompact ?? true,
11163
11335
  contextStrategy: cfg.contextStrategy ?? "hybrid",
11164
11336
  logLevel: cfg.logLevel ?? "info",
@@ -12078,6 +12250,7 @@ User message:
12078
12250
  featureMemory: state.settingsPicker.featureMemory,
12079
12251
  featureSkills: state.settingsPicker.featureSkills,
12080
12252
  featureModelsRegistry: state.settingsPicker.featureModelsRegistry,
12253
+ featureTokenSaving: state.settingsPicker.featureTokenSaving,
12081
12254
  contextAutoCompact: state.settingsPicker.contextAutoCompact,
12082
12255
  contextStrategy: state.settingsPicker.contextStrategy,
12083
12256
  logLevel: state.settingsPicker.logLevel,
@@ -12248,6 +12421,7 @@ User message:
12248
12421
  subagentCount: Object.keys(state.fleet).length,
12249
12422
  processCount: getProcessRegistry().activeCount,
12250
12423
  hiddenItems,
12424
+ events,
12251
12425
  eternalStage: state.eternalStage,
12252
12426
  goalSummary: state.goalSummary,
12253
12427
  indexState,
@@ -12257,7 +12431,9 @@ User message:
12257
12431
  nextStepsAutoSubmitCountdown,
12258
12432
  autoProceedCountdown: state.countdown?.remainingSeconds ?? null,
12259
12433
  sessionCount,
12260
- mailbox: mailboxStatus
12434
+ mailbox: mailboxStatus,
12435
+ tokenSavingMode: getSettings ? getSettings().featureTokenSaving : tokenSavingMode,
12436
+ toolCount
12261
12437
  }
12262
12438
  ) }),
12263
12439
  /* @__PURE__ */ jsx(
@@ -12523,6 +12699,7 @@ async function runTui(opts) {
12523
12699
  const cleanup = () => {
12524
12700
  if (cleaned) return;
12525
12701
  cleaned = true;
12702
+ unregisterTuiClient();
12526
12703
  unsilenceTerminal();
12527
12704
  try {
12528
12705
  stopTitle();
@@ -12549,6 +12726,64 @@ async function runTui(opts) {
12549
12726
  }
12550
12727
  process.off("exit", exitHandler);
12551
12728
  };
12729
+ let clientHeartbeatTimer = null;
12730
+ let clientSyncTimer = null;
12731
+ const CLIENT_HEARTBEAT_MS = 15e3;
12732
+ const CLIENT_SYNC_MS = 3e4;
12733
+ const registerTuiClient = async () => {
12734
+ if (!opts.projectRoot) return null;
12735
+ try {
12736
+ const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot());
12737
+ const mailbox = new GlobalMailbox(projectDir, opts.events);
12738
+ const clientId = `tui@${randomUUID().slice(0, 8)}`;
12739
+ await mailbox.registerClient({
12740
+ clientId,
12741
+ sessionId: opts.projectRoot,
12742
+ name: `TUI [${path4.basename(opts.projectRoot)}]`,
12743
+ source: "tui",
12744
+ pid: process.pid
12745
+ });
12746
+ clientHeartbeatTimer = setInterval(() => {
12747
+ mailbox.clientHeartbeat({ clientId }).catch(() => {
12748
+ });
12749
+ }, CLIENT_HEARTBEAT_MS);
12750
+ clientHeartbeatTimer.unref();
12751
+ const syncClients = async () => {
12752
+ try {
12753
+ const statuses = await mailbox.getClientStatuses();
12754
+ const counts = { tui: 0, webui: 0, repl: 0 };
12755
+ for (const s2 of statuses) {
12756
+ if (s2.online && s2.source in counts) {
12757
+ counts[s2.source]++;
12758
+ }
12759
+ }
12760
+ opts.events.emitCustom("mailbox.sync_clients", counts);
12761
+ } catch {
12762
+ }
12763
+ };
12764
+ setTimeout(() => {
12765
+ void syncClients();
12766
+ }, 5e3);
12767
+ clientSyncTimer = setInterval(() => {
12768
+ void syncClients();
12769
+ }, CLIENT_SYNC_MS);
12770
+ clientSyncTimer.unref();
12771
+ return clientId;
12772
+ } catch {
12773
+ return null;
12774
+ }
12775
+ };
12776
+ const unregisterTuiClient = () => {
12777
+ if (clientHeartbeatTimer) {
12778
+ clearInterval(clientHeartbeatTimer);
12779
+ clientHeartbeatTimer = null;
12780
+ }
12781
+ if (clientSyncTimer) {
12782
+ clearInterval(clientSyncTimer);
12783
+ clientSyncTimer = null;
12784
+ }
12785
+ };
12786
+ registerTuiClient();
12552
12787
  return new Promise((resolve) => {
12553
12788
  let exitCode = 0;
12554
12789
  let hardExitTimer = null;
@@ -12607,6 +12842,7 @@ async function runTui(opts) {
12607
12842
  fleetRoster: opts.fleetRoster,
12608
12843
  onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory?.(dispatch) : void 0,
12609
12844
  fleetStreamController: opts.fleetStreamController,
12845
+ interruptController: opts.interruptController,
12610
12846
  enhanceController: opts.enhanceController,
12611
12847
  enhanceEnabled: opts.enhanceController?.enabled ?? true,
12612
12848
  statuslineHiddenItems: opts.statuslineHiddenItems,
@@ -12626,6 +12862,8 @@ async function runTui(opts) {
12626
12862
  confirmExit: opts.confirmExit,
12627
12863
  mouse: mouseEnabled,
12628
12864
  modeLabel: opts.modeLabel,
12865
+ tokenSavingMode: opts.tokenSavingMode,
12866
+ toolCount: opts.toolCount,
12629
12867
  getModeLabel: opts.getModeLabel,
12630
12868
  registerDebugStreamCallback: opts.registerDebugStreamCallback,
12631
12869
  restoreDebugStreamCallback: opts.restoreDebugStreamCallback,