@wrongstack/tui 0.255.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.d.ts CHANGED
@@ -153,6 +153,8 @@ type Settings = {
153
153
  featureMemory: boolean;
154
154
  featureSkills: boolean;
155
155
  featureModelsRegistry: boolean;
156
+ /** Token-saving mode: omits non-essential tools and trims system prompt. */
157
+ featureTokenSaving: boolean;
156
158
  contextAutoCompact: boolean;
157
159
  contextStrategy: 'hybrid' | 'intelligent' | 'selective';
158
160
  logLevel: 'error' | 'warn' | 'info' | 'debug' | 'trace';
@@ -269,6 +271,10 @@ interface RunTuiOptions {
269
271
  confirmExit?: boolean | undefined;
270
272
  /** Active agent mode label shown in the status bar (e.g. "teach", "brief"). */
271
273
  modeLabel?: string | undefined;
274
+ /** Token-saving mode indicator — shown in the TUI status bar. */
275
+ tokenSavingMode?: boolean | undefined;
276
+ /** Number of registered tools — shown on the status bar line 2. */
277
+ toolCount?: number | undefined;
272
278
  /** Live getter for the agent mode label so the status bar updates after /mode. */
273
279
  getModeLabel?: (() => string) | undefined;
274
280
  /**
@@ -322,6 +328,13 @@ interface RunTuiOptions {
322
328
  enabled: boolean;
323
329
  setEnabled: (enabled: boolean) => void;
324
330
  } | undefined;
331
+ /**
332
+ * Controller for the `/interrupt` slash command. The App installs the real
333
+ * `abortLeader` on mount so the command can abort the in-flight leader run.
334
+ */
335
+ interruptController?: {
336
+ abortLeader: () => boolean;
337
+ } | undefined;
325
338
  /**
326
339
  * Controller for the `/enhance on|off` prompt-refinement toggle. The App
327
340
  * installs a dispatch-backed `setEnabled` here on mount so the slash command
@@ -338,8 +351,8 @@ interface RunTuiOptions {
338
351
  * visible bar without a round-trip. The initial value is loaded from
339
352
  * the config file before App mounts.
340
353
  */
341
- statuslineHiddenItems: Array<'todos' | 'plan' | 'tasks' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>;
342
- setStatuslineHiddenItems: (items: Array<'todos' | 'plan' | 'tasks' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>) => void;
354
+ statuslineHiddenItems: Array<'todos' | 'plan' | 'tasks' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost' | 'working_dir'>;
355
+ setStatuslineHiddenItems: (items: Array<'todos' | 'plan' | 'tasks' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost' | 'working_dir'>) => void;
343
356
  /**
344
357
  * Controller for the agents monitor overlay. App installs a dispatch-backed
345
358
  * setter on mount so the `/agents on|off` slash command can toggle the
@@ -541,16 +554,22 @@ declare function replaySessionEvents(events: SessionEvent[], startId: number): H
541
554
  * Unified next-steps suggestion parser.
542
555
  *
543
556
  * Three code paths feed into the suggestion store:
544
- * 1. TUI rendering — entry.tsx parses "💡 Next steps" from assistant output
545
- * 2. REPL store — repl.ts parses "💡 Next steps" from final agent output
557
+ * 1. TUI rendering — entry.tsx parses "💡 Next steps" or "<next_steps>" from assistant output
558
+ * 2. REPL store — repl.ts parses "💡 Next steps" or "<next_steps>" from final agent output
546
559
  * 3. /suggest output — suggest.ts parses LLM-generated numbered lists
547
560
  *
548
561
  * Heading mode (`requireHeading = true`):
549
- * strict=true — only 💡 emoji heading (TUI rendering)
550
- * strict=false — 💡, ##, plain "Next steps" headings (REPL store)
562
+ * strict=true — only 💡 emoji heading or <next_steps> tag (TUI rendering)
563
+ * strict=false — 💡, ##, plain "Next steps", or <next_steps> headings (REPL store)
551
564
  *
552
565
  * Raw mode (`requireHeading = false`):
553
566
  * Parses numbered/bullet items from anywhere in text (subagent /suggest output).
567
+ *
568
+ * Supported formats:
569
+ * 💡 Next steps (old emoji format)
570
+ * ## Next steps (markdown heading)
571
+ * Next steps (plain text)
572
+ * <next_steps> (new XML tag format - preferred)
554
573
  */
555
574
  interface ParsedNextStep {
556
575
  index: number;
@@ -562,16 +581,16 @@ interface ParseNextStepsResult {
562
581
  /** Flat string array — what gets stored in the suggestion store. */
563
582
  texts: string[];
564
583
  /**
565
- * Content with the entire "💡 Next steps" block removed.
584
+ * Content with the entire "💡 Next steps" or "<next_steps>" block removed.
566
585
  * Used by entry.tsx to strip suggestions from the rendered message body.
567
586
  */
568
587
  stripped: string;
569
588
  }
570
589
  /**
571
- * Parse "💡 Next steps" blocks from assistant output (or raw numbered lines).
590
+ * Parse "<next_steps>" or "💡 Next steps" blocks from assistant output (or raw numbered lines).
572
591
  *
573
592
  * @param content — raw assistant message text or subagent output
574
- * @param strict — when true, only the 💡 emoji heading is accepted (TUI rendering).
593
+ * @param strict — when true, accepts 💡 emoji heading OR <next_steps> XML tag (TUI rendering).
575
594
  * when false, also accepts ## / plain "Next steps" headings (REPL store).
576
595
  * @param requireHeading — when true, a heading must precede the item list.
577
596
  * when false, numbered/bullet items are parsed from anywhere in text
package/dist/index.js CHANGED
@@ -163,7 +163,9 @@ function StatusBar({
163
163
  nextStepsAutoSubmitCountdown,
164
164
  autoProceedCountdown,
165
165
  sessionCount,
166
- mailbox
166
+ mailbox,
167
+ tokenSavingMode,
168
+ toolCount
167
169
  }) {
168
170
  const { stdout } = useStdout();
169
171
  const [termWidth, setTermWidth] = useState(stdout?.columns ?? 90);
@@ -202,7 +204,7 @@ function StatusBar({
202
204
  const statePrefix = state === "idle" || state === "aborting" ? "\u25CF" : spinner;
203
205
  const thinking = state === "running" || state === "streaming";
204
206
  const hasAutoProceed = autoProceedCountdown != null && autoProceedCountdown > 0;
205
- 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;
206
208
  const fleetHasActivity = fleet && (fleet.running > 0 || fleet.idle > 0 || fleet.pending > 0 || fleet.completed > 0) || subagentCount > 0;
207
209
  const hasBrainActivity = !!brain && brain.state !== "idle";
208
210
  const hasDebugStream = !!debugStreamStats;
@@ -423,6 +425,19 @@ function StatusBar({
423
425
  " session",
424
426
  sessionCount === 1 ? "" : "s"
425
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" })
426
441
  ] }) : null
427
442
  ] }) : /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
428
443
  hasThirdLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
@@ -2846,7 +2861,7 @@ function MarkdownView({
2846
2861
  const tableEnd = detectTable(lines, i);
2847
2862
  if (tableEnd > i) {
2848
2863
  rows.push(
2849
- /* @__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++}`)
2850
2865
  );
2851
2866
  i = tableEnd;
2852
2867
  continue;
@@ -2866,10 +2881,14 @@ function MarkdownView({
2866
2881
  rows.push(
2867
2882
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2868
2883
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
2869
- /[\u2500-\u257F]/.test(qContent) ? /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: [...qContent].slice(0, (contentWidth ?? termWidth) - 2).map((ch, ci) => (
2870
- /* biome-ignore lint/suspicious/noArrayIndexKey: characters are not reorderable */
2871
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ch }, ci)
2872
- )) }) : /* @__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 })
2873
2892
  ] }, `q${key++}`)
2874
2893
  );
2875
2894
  continue;
@@ -2898,7 +2917,7 @@ function MarkdownView({
2898
2917
  const maxW = contentWidth ?? termWidth;
2899
2918
  const chars = [...line].slice(0, maxW);
2900
2919
  rows.push(
2901
- /* @__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) => (
2902
2921
  /* biome-ignore lint/suspicious/noArrayIndexKey: characters are not reorderable */
2903
2922
  /* @__PURE__ */ jsx(Text, { children: ch }, ci)
2904
2923
  )) }, `bx${key++}`)
@@ -3800,11 +3819,12 @@ function Banner({
3800
3819
  }
3801
3820
 
3802
3821
  // src/components/suggestions.ts
3803
- var STRICT_HEADING_RE = /💡\s*Next steps?\s*\n+/i;
3822
+ var STRICT_HEADING_RE = /(?:💡\s*Next steps?|<next_steps>)\s*\n+/i;
3804
3823
  var PERMISSIVE_HEADING_PATTERNS = [
3805
3824
  { re: /💡\s*Next steps?\s*\n+/i, label: "emoji" },
3806
3825
  { re: /##?\s*Next steps?\s*\n+/i, label: "markdown" },
3807
- { 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" }
3808
3828
  ];
3809
3829
  var ITEM_RE = /^(?:(\d+)[.)]\s*|[-*•]\s*)(.+)$/;
3810
3830
  var MAX_STEPS = 6;
@@ -3888,6 +3908,10 @@ function findBlockEnd(afterHeading, stepCount) {
3888
3908
  let found = 0;
3889
3909
  for (const rawLine of lines) {
3890
3910
  const line = rawLine.trim();
3911
+ if (line === "</next_steps>") {
3912
+ consumed += rawLine.length + 1;
3913
+ break;
3914
+ }
3891
3915
  if (!line) {
3892
3916
  consumed += rawLine.length + 1;
3893
3917
  continue;
@@ -3952,6 +3976,7 @@ var Entry = React5.memo(function Entry2({
3952
3976
  borderRight: false,
3953
3977
  borderBottom: false,
3954
3978
  borderColor: theme.user,
3979
+ backgroundColor: "#1e1e2e",
3955
3980
  paddingLeft: 1,
3956
3981
  children: /* @__PURE__ */ jsxs(Text, { children: [
3957
3982
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.user, children: "USER " }),
@@ -3981,8 +4006,9 @@ var Entry = React5.memo(function Entry2({
3981
4006
  borderStyle: "single",
3982
4007
  borderTop: false,
3983
4008
  borderRight: false,
3984
- borderBottom: hasNext ? false : void 0,
4009
+ borderBottom: false,
3985
4010
  borderColor: theme.assistant,
4011
+ backgroundColor: "#1e1e2e",
3986
4012
  paddingLeft: 1,
3987
4013
  children: [
3988
4014
  /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }) }),
@@ -5543,7 +5569,7 @@ var MODE_DESC = {
5543
5569
  suggest: "Shows next-step suggestions after each turn",
5544
5570
  auto: "Self-driving \u2014 agent continues automatically"
5545
5571
  };
5546
- var SETTINGS_FIELD_COUNT = 25;
5572
+ var SETTINGS_FIELD_COUNT = 26;
5547
5573
  var CONFIG_SCOPES = ["global", "project"];
5548
5574
  function SettingsPicker({
5549
5575
  field,
@@ -5560,6 +5586,7 @@ function SettingsPicker({
5560
5586
  featureMemory,
5561
5587
  featureSkills,
5562
5588
  featureModelsRegistry,
5589
+ featureTokenSaving,
5563
5590
  contextAutoCompact,
5564
5591
  contextStrategy,
5565
5592
  logLevel,
@@ -5643,6 +5670,11 @@ function SettingsPicker({
5643
5670
  value: boolVal(featureModelsRegistry),
5644
5671
  detail: "Fetch models.dev catalog at startup"
5645
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
+ },
5646
5678
  // ── Context ──
5647
5679
  { section: "Context" },
5648
5680
  {
@@ -6743,6 +6775,36 @@ function handleCollabDone(event, dispatch, stateRef) {
6743
6775
  verdict: payload.report.overallVerdict ?? "needs_revision"
6744
6776
  });
6745
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
+ }
6746
6808
  function useTuiControllers({
6747
6809
  dispatch,
6748
6810
  streamFleet,
@@ -7809,6 +7871,7 @@ function reducer(state, action) {
7809
7871
  featureMemory: action.featureMemory,
7810
7872
  featureSkills: action.featureSkills,
7811
7873
  featureModelsRegistry: action.featureModelsRegistry,
7874
+ featureTokenSaving: action.featureTokenSaving,
7812
7875
  contextAutoCompact: action.contextAutoCompact,
7813
7876
  contextStrategy: action.contextStrategy,
7814
7877
  logLevel: action.logLevel,
@@ -7866,53 +7929,54 @@ function reducer(state, action) {
7866
7929
  if (f === 10) return { ...state, settingsPicker: { ...sp, featureMemory: !sp.featureMemory, hint: void 0 } };
7867
7930
  if (f === 11) return { ...state, settingsPicker: { ...sp, featureSkills: !sp.featureSkills, hint: void 0 } };
7868
7931
  if (f === 12) return { ...state, settingsPicker: { ...sp, featureModelsRegistry: !sp.featureModelsRegistry, hint: void 0 } };
7869
- if (f === 13) return { ...state, settingsPicker: { ...sp, contextAutoCompact: !sp.contextAutoCompact, hint: void 0 } };
7870
- 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) {
7871
7935
  const i = COMPACTOR_STRATEGIES.indexOf(sp.contextStrategy);
7872
7936
  const base = i < 0 ? 0 : i;
7873
7937
  const next = (base + action.delta + COMPACTOR_STRATEGIES.length) % COMPACTOR_STRATEGIES.length;
7874
7938
  return { ...state, settingsPicker: { ...sp, contextStrategy: expectDefined$1(COMPACTOR_STRATEGIES[next]), hint: void 0 } };
7875
7939
  }
7876
- if (f === 15) {
7940
+ if (f === 16) {
7877
7941
  const i = LOG_LEVELS.indexOf(sp.logLevel);
7878
7942
  const base = i < 0 ? 0 : i;
7879
7943
  const next = (base + action.delta + LOG_LEVELS.length) % LOG_LEVELS.length;
7880
7944
  return { ...state, settingsPicker: { ...sp, logLevel: expectDefined$1(LOG_LEVELS[next]), hint: void 0 } };
7881
7945
  }
7882
- if (f === 16) {
7946
+ if (f === 17) {
7883
7947
  const i = AUDIT_LEVELS.indexOf(sp.auditLevel);
7884
7948
  const base = i < 0 ? 0 : i;
7885
7949
  const next = (base + action.delta + AUDIT_LEVELS.length) % AUDIT_LEVELS.length;
7886
7950
  return { ...state, settingsPicker: { ...sp, auditLevel: expectDefined$1(AUDIT_LEVELS[next]), hint: void 0 } };
7887
7951
  }
7888
- if (f === 17) return { ...state, settingsPicker: { ...sp, indexOnStart: !sp.indexOnStart, hint: void 0 } };
7889
- if (f === 18) {
7952
+ if (f === 18) return { ...state, settingsPicker: { ...sp, indexOnStart: !sp.indexOnStart, hint: void 0 } };
7953
+ if (f === 19) {
7890
7954
  const j = MAX_ITERATIONS_PRESETS.indexOf(sp.maxIterations);
7891
7955
  const base = j < 0 ? 0 : j;
7892
7956
  const next = (base + action.delta + MAX_ITERATIONS_PRESETS.length) % MAX_ITERATIONS_PRESETS.length;
7893
7957
  return { ...state, settingsPicker: { ...sp, maxIterations: expectDefined$1(MAX_ITERATIONS_PRESETS[next]), hint: void 0 } };
7894
7958
  }
7895
- if (f === 19) {
7959
+ if (f === 20) {
7896
7960
  const aj = AUTO_PROCEED_MAX_PRESETS.indexOf(sp.autoProceedMaxIterations);
7897
7961
  const abase = aj < 0 ? 0 : aj;
7898
7962
  const anext = (abase + action.delta + AUTO_PROCEED_MAX_PRESETS.length) % AUTO_PROCEED_MAX_PRESETS.length;
7899
7963
  return { ...state, settingsPicker: { ...sp, autoProceedMaxIterations: expectDefined$1(AUTO_PROCEED_MAX_PRESETS[anext]), hint: void 0 } };
7900
7964
  }
7901
- if (f === 20) {
7965
+ if (f === 21) {
7902
7966
  const ej = ENHANCE_DELAY_PRESETS.indexOf(sp.enhanceDelayMs);
7903
7967
  const ebase = ej < 0 ? 0 : ej;
7904
7968
  const enext = (ebase + action.delta + ENHANCE_DELAY_PRESETS.length) % ENHANCE_DELAY_PRESETS.length;
7905
7969
  return { ...state, settingsPicker: { ...sp, enhanceDelayMs: expectDefined$1(ENHANCE_DELAY_PRESETS[enext]), hint: void 0 } };
7906
7970
  }
7907
- if (f === 21) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
7908
- if (f === 22) {
7971
+ if (f === 22) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
7972
+ if (f === 23) {
7909
7973
  const i = ENHANCE_LANGUAGES.indexOf(sp.enhanceLanguage);
7910
7974
  const base = i < 0 ? 0 : i;
7911
7975
  const next = (base + action.delta + ENHANCE_LANGUAGES.length) % ENHANCE_LANGUAGES.length;
7912
7976
  return { ...state, settingsPicker: { ...sp, enhanceLanguage: expectDefined$1(ENHANCE_LANGUAGES[next]), hint: void 0 } };
7913
7977
  }
7914
- if (f === 23) return { ...state, settingsPicker: { ...sp, debugStream: !sp.debugStream, hint: void 0 } };
7915
- if (f === 24) {
7978
+ if (f === 24) return { ...state, settingsPicker: { ...sp, debugStream: !sp.debugStream, hint: void 0 } };
7979
+ if (f === 25) {
7916
7980
  const i = CONFIG_SCOPES.indexOf(sp.configScope);
7917
7981
  const base = i < 0 ? 0 : i;
7918
7982
  const next = (base + action.delta + CONFIG_SCOPES.length) % CONFIG_SCOPES.length;
@@ -8751,6 +8815,8 @@ function App({
8751
8815
  provider,
8752
8816
  family,
8753
8817
  keyTail,
8818
+ tokenSavingMode,
8819
+ toolCount,
8754
8820
  getPickableProviders,
8755
8821
  switchProviderAndModel,
8756
8822
  getSettings,
@@ -8768,6 +8834,7 @@ function App({
8768
8834
  listSessions,
8769
8835
  onResumeSession,
8770
8836
  fleetStreamController,
8837
+ interruptController,
8771
8838
  statuslineHiddenItems,
8772
8839
  setStatuslineHiddenItems,
8773
8840
  agentsMonitorController,
@@ -8788,14 +8855,32 @@ function App({
8788
8855
  }) {
8789
8856
  const { exit } = useApp();
8790
8857
  const { stdout } = useStdout();
8791
- const [liveModel, setLiveModel] = useState(model);
8792
- const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
8793
- const [activeMaxContext, setActiveMaxContext] = useState(effectiveMaxContext);
8794
- const [yoloLive, setYoloLive] = useState(yolo);
8795
- const [autonomyLive, setAutonomyLive] = useState(getAutonomy?.() ?? "off");
8796
- const [liveModeLabel, setLiveModeLabel] = useState(modeLabel ?? "");
8797
- const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
8798
- 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
+ });
8799
8884
  const prevBranchRef = useRef(null);
8800
8885
  const [indexState, setIndexState] = useState(() => getIndexState());
8801
8886
  useEffect(() => {
@@ -8803,7 +8888,7 @@ function App({
8803
8888
  return onIndexStateChange((next) => setIndexState(next));
8804
8889
  }, []);
8805
8890
  useEffect(() => {
8806
- setHiddenItems(statuslineHiddenItems);
8891
+ setHiddenItems([...statuslineHiddenItems]);
8807
8892
  }, [statuslineHiddenItems]);
8808
8893
  useEffect(() => {
8809
8894
  setStatuslineHiddenItems(hiddenItems);
@@ -8899,7 +8984,7 @@ function App({
8899
8984
  },
8900
8985
  autonomyPicker: { open: false, options: [], selected: 0 },
8901
8986
  resumePicker: { open: false, sessions: [], selected: 0, busy: false, hint: void 0, error: void 0 },
8902
- 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" },
8903
8988
  projectPicker: { open: false, allItems: [], items: [], selected: 0, filter: "", hint: void 0 },
8904
8989
  confirmQueue: [],
8905
8990
  enhance: null,
@@ -9531,6 +9616,7 @@ function App({
9531
9616
  featureMemory: sp.featureMemory,
9532
9617
  featureSkills: sp.featureSkills,
9533
9618
  featureModelsRegistry: sp.featureModelsRegistry,
9619
+ featureTokenSaving: sp.featureTokenSaving,
9534
9620
  contextAutoCompact: sp.contextAutoCompact,
9535
9621
  contextStrategy: sp.contextStrategy,
9536
9622
  logLevel: sp.logLevel,
@@ -9908,6 +9994,7 @@ function App({
9908
9994
  featureMemory: s2.featureMemory ?? true,
9909
9995
  featureSkills: s2.featureSkills ?? true,
9910
9996
  featureModelsRegistry: s2.featureModelsRegistry ?? true,
9997
+ featureTokenSaving: s2.featureTokenSaving ?? false,
9911
9998
  contextAutoCompact: s2.contextAutoCompact ?? true,
9912
9999
  contextStrategy: s2.contextStrategy ?? "hybrid",
9913
10000
  logLevel: s2.logLevel ?? "info",
@@ -10025,6 +10112,7 @@ function App({
10025
10112
  featureMemory: sp.featureMemory,
10026
10113
  featureSkills: sp.featureSkills,
10027
10114
  featureModelsRegistry: sp.featureModelsRegistry,
10115
+ featureTokenSaving: sp.featureTokenSaving,
10028
10116
  contextAutoCompact: sp.contextAutoCompact,
10029
10117
  contextStrategy: sp.contextStrategy,
10030
10118
  logLevel: sp.logLevel,
@@ -10234,6 +10322,15 @@ function App({
10234
10322
  entry: { kind: "error", text: e.description }
10235
10323
  });
10236
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
+ });
10237
10334
  const offProvResp = events.on("provider.response", () => {
10238
10335
  const text = streamingTextRef.current;
10239
10336
  streamingTextRef.current = "";
@@ -10307,6 +10404,7 @@ function App({
10307
10404
  offTool();
10308
10405
  offRetry();
10309
10406
  offProvErr();
10407
+ offFallback();
10310
10408
  offProvResp();
10311
10409
  offConfirmNeeded();
10312
10410
  offTrustPersisted();
@@ -10368,6 +10466,15 @@ function App({
10368
10466
  enhanceController,
10369
10467
  agentsMonitorController
10370
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]);
10371
10478
  const lastEscAtRef = useRef(0);
10372
10479
  const ESC_DOUBLE_PRESS_MS = 1e3;
10373
10480
  useDirectorFleetBridge({
@@ -11223,6 +11330,7 @@ function App({
11223
11330
  featureMemory: cfg.featureMemory ?? true,
11224
11331
  featureSkills: cfg.featureSkills ?? true,
11225
11332
  featureModelsRegistry: cfg.featureModelsRegistry ?? true,
11333
+ featureTokenSaving: cfg.featureTokenSaving ?? false,
11226
11334
  contextAutoCompact: cfg.contextAutoCompact ?? true,
11227
11335
  contextStrategy: cfg.contextStrategy ?? "hybrid",
11228
11336
  logLevel: cfg.logLevel ?? "info",
@@ -12142,6 +12250,7 @@ User message:
12142
12250
  featureMemory: state.settingsPicker.featureMemory,
12143
12251
  featureSkills: state.settingsPicker.featureSkills,
12144
12252
  featureModelsRegistry: state.settingsPicker.featureModelsRegistry,
12253
+ featureTokenSaving: state.settingsPicker.featureTokenSaving,
12145
12254
  contextAutoCompact: state.settingsPicker.contextAutoCompact,
12146
12255
  contextStrategy: state.settingsPicker.contextStrategy,
12147
12256
  logLevel: state.settingsPicker.logLevel,
@@ -12322,7 +12431,9 @@ User message:
12322
12431
  nextStepsAutoSubmitCountdown,
12323
12432
  autoProceedCountdown: state.countdown?.remainingSeconds ?? null,
12324
12433
  sessionCount,
12325
- mailbox: mailboxStatus
12434
+ mailbox: mailboxStatus,
12435
+ tokenSavingMode: getSettings ? getSettings().featureTokenSaving : tokenSavingMode,
12436
+ toolCount
12326
12437
  }
12327
12438
  ) }),
12328
12439
  /* @__PURE__ */ jsx(
@@ -12731,6 +12842,7 @@ async function runTui(opts) {
12731
12842
  fleetRoster: opts.fleetRoster,
12732
12843
  onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory?.(dispatch) : void 0,
12733
12844
  fleetStreamController: opts.fleetStreamController,
12845
+ interruptController: opts.interruptController,
12734
12846
  enhanceController: opts.enhanceController,
12735
12847
  enhanceEnabled: opts.enhanceController?.enabled ?? true,
12736
12848
  statuslineHiddenItems: opts.statuslineHiddenItems,
@@ -12750,6 +12862,8 @@ async function runTui(opts) {
12750
12862
  confirmExit: opts.confirmExit,
12751
12863
  mouse: mouseEnabled,
12752
12864
  modeLabel: opts.modeLabel,
12865
+ tokenSavingMode: opts.tokenSavingMode,
12866
+ toolCount: opts.toolCount,
12753
12867
  getModeLabel: opts.getModeLabel,
12754
12868
  registerDebugStreamCallback: opts.registerDebugStreamCallback,
12755
12869
  restoreDebugStreamCallback: opts.restoreDebugStreamCallback,