@wrongstack/tui 0.264.0 → 0.265.1

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,15 +1,16 @@
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';
1
+ import { writeErr, resolveProjectDir, wstackGlobalRoot, GlobalMailbox, resolveWstackPaths, loadGoal, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, projectSlug, 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
4
  import { randomUUID } from 'crypto';
5
- import * as path4 from 'path';
5
+ import * as path5 from 'path';
6
6
  import React5, { forwardRef, useState, useEffect, useMemo, memo, useRef, useCallback, useReducer, useLayoutEffect } from 'react';
7
- import * as fs2 from 'fs/promises';
8
- import { expectDefined, toErrorMessage } from '@wrongstack/core/utils';
7
+ import * as fs3 from 'fs/promises';
8
+ import { expectDefined, toErrorMessage, resolveWstackPaths as resolveWstackPaths$1 } from '@wrongstack/core/utils';
9
9
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
10
10
  import { getIndexState, onIndexStateChange, getProcessRegistry } from '@wrongstack/tools';
11
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
12
  import { readClipboardText, readClipboardImage } from '@wrongstack/runtime/clipboard';
13
+ import { getToolIcon, TOOL_ICON_CONFIG } from '@wrongstack/tools/tool-icons';
13
14
  import * as v8 from 'v8';
14
15
  import { spawn } from 'child_process';
15
16
 
@@ -166,6 +167,7 @@ function StatusBar({
166
167
  eternalStage,
167
168
  goalSummary,
168
169
  indexState,
170
+ breakerCountdown,
169
171
  modeLabel,
170
172
  debugStreamStats,
171
173
  enhanceCountdown,
@@ -340,7 +342,21 @@ function StatusBar({
340
342
  ] }) : indexState?.circuit?.state === "open" ? /* @__PURE__ */ jsxs(Fragment, { children: [
341
343
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
342
344
  /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2699 index paused (/reindex)" })
343
- ] }) : null
345
+ ] }) : null,
346
+ breakerCountdown ? (() => {
347
+ const secs = Math.ceil(breakerCountdown.remainingMs / 1e3);
348
+ const ratio = breakerCountdown.totalMs > 0 ? breakerCountdown.remainingMs / breakerCountdown.totalMs : 0;
349
+ const c = secs > 20 ? "green" : secs > 10 ? "yellow" : "red";
350
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
351
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
352
+ /* @__PURE__ */ jsxs(Text, { color: c, bold: true, children: [
353
+ "\u26A1 kill/reset in ",
354
+ secs,
355
+ "s",
356
+ ratio <= 0.25 ? "!" : ""
357
+ ] })
358
+ ] });
359
+ })() : null
344
360
  ] })
345
361
  ) }),
346
362
  hasSecondLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
@@ -491,6 +507,11 @@ function StatusBar({
491
507
  plan.done > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
492
508
  "\u2713",
493
509
  plan.done
510
+ ] }) : null,
511
+ plan.scope ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
512
+ " [",
513
+ plan.scope,
514
+ "]"
494
515
  ] }) : null
495
516
  ] })
496
517
  ] }) : null,
@@ -521,6 +542,11 @@ function StatusBar({
521
542
  tasks.failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
522
543
  "\u2717",
523
544
  tasks.failed
545
+ ] }) : null,
546
+ tasks.scope ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
547
+ " [",
548
+ tasks.scope,
549
+ "]"
524
550
  ] }) : null
525
551
  ] })
526
552
  ] }) : null,
@@ -1765,8 +1791,8 @@ function FilePicker({ query, matches, selected }) {
1765
1791
  ] }, m))
1766
1792
  ] });
1767
1793
  }
1768
- function highlight(path6, _query) {
1769
- return path6;
1794
+ function highlight(path7, _query) {
1795
+ return path7;
1770
1796
  }
1771
1797
  function FleetPanel({
1772
1798
  entries,
@@ -1930,6 +1956,36 @@ function MailboxPanel({
1930
1956
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/mailbox \u2014 Esc to close" }) })
1931
1957
  ] });
1932
1958
  }
1959
+ var TOOL_GLYPHS = {
1960
+ file: "\u25A4",
1961
+ edit: "\u270E",
1962
+ search: "\u2315",
1963
+ folder: "\u25A3",
1964
+ terminal: "\u25B8",
1965
+ web: "\u25C8",
1966
+ git: "\u2387",
1967
+ tree: "\u2630",
1968
+ code: "\u2699",
1969
+ test: "\u2697",
1970
+ package: "\u2B22",
1971
+ document: "\u2637",
1972
+ scaffold: "\u2731",
1973
+ todo: "\u2610",
1974
+ plan: "\u2756",
1975
+ task: "\u25AA",
1976
+ meta: "\u274F",
1977
+ index: "\u229B",
1978
+ json: "\u2317",
1979
+ diff: "\u2206",
1980
+ logs: "\u2261",
1981
+ settings: "\u2699",
1982
+ brain: "\u2726",
1983
+ fallback: "\u2022"
1984
+ };
1985
+ function getToolVisual(name) {
1986
+ const id = getToolIcon(name);
1987
+ return { glyph: TOOL_GLYPHS[id], color: TOOL_ICON_CONFIG[id].color };
1988
+ }
1933
1989
  function helpSections() {
1934
1990
  const nav = [];
1935
1991
  nav.push(
@@ -1976,31 +2032,133 @@ function helpSections() {
1976
2032
  { keys: "/settings", desc: "autonomy defaults (also Ctrl+S)" },
1977
2033
  { keys: "/clear", desc: "clear the conversation" }
1978
2034
  ]
2035
+ },
2036
+ {
2037
+ title: "Tool Colors",
2038
+ entries: toolColorLegend()
1979
2039
  }
1980
2040
  ];
1981
2041
  }
2042
+ function toolColorLegend() {
2043
+ const tools = [
2044
+ // File operations (most common)
2045
+ { name: "read/write", tool: "read", desc: "file I/O" },
2046
+ { name: "write", tool: "write", desc: "create file" },
2047
+ { name: "edit", tool: "edit", desc: "edit file" },
2048
+ { name: "patch", tool: "patch", desc: "diff/patch" },
2049
+ // Search
2050
+ { name: "search", tool: "grep", desc: "search" },
2051
+ { name: "glob", tool: "glob", desc: "glob/pattern" },
2052
+ // Shell & web
2053
+ { name: "terminal", tool: "bash", desc: "shell" },
2054
+ { name: "web", tool: "fetch", desc: "web" },
2055
+ // Navigation & tree
2056
+ { name: "folder", tool: "ls", desc: "navigate" },
2057
+ { name: "tree", tool: "tree", desc: "tree view" },
2058
+ // VCS
2059
+ { name: "git", tool: "git", desc: "git" },
2060
+ // Code quality
2061
+ { name: "lint", tool: "lint", desc: "lint" },
2062
+ { name: "format", tool: "format", desc: "format" },
2063
+ { name: "typecheck", tool: "typecheck", desc: "typecheck" },
2064
+ // Testing & packages
2065
+ { name: "test", tool: "test", desc: "test" },
2066
+ { name: "package", tool: "install", desc: "packages" },
2067
+ { name: "audit", tool: "audit", desc: "audit" },
2068
+ // Planning & tracking
2069
+ { name: "todo", tool: "todo", desc: "todos" },
2070
+ { name: "plan", tool: "plan", desc: "planning" },
2071
+ { name: "task", tool: "task", desc: "tasks" },
2072
+ // Docs & scaffolding
2073
+ { name: "document", tool: "document", desc: "docs" },
2074
+ { name: "scaffold", tool: "scaffold", desc: "scaffold" },
2075
+ // Data & logs
2076
+ { name: "json", tool: "json", desc: "JSON" },
2077
+ { name: "logs", tool: "logs", desc: "logs" },
2078
+ // Memory & meta
2079
+ { name: "brain", tool: "remember", desc: "memory" },
2080
+ { name: "tool_use", tool: "tool_use", desc: "tool chain" }
2081
+ ];
2082
+ return tools.map(({ name, tool, desc }) => {
2083
+ const { glyph, color } = getToolVisual(tool);
2084
+ return {
2085
+ keys: `${glyph} ${name}`,
2086
+ desc: `${desc} (${color})`
2087
+ };
2088
+ });
2089
+ }
2090
+ function splitIntoColumns(entries) {
2091
+ const left = [];
2092
+ const right = [];
2093
+ for (const entry of entries) {
2094
+ if (left.length <= right.length) {
2095
+ left.push(entry);
2096
+ } else {
2097
+ right.push(entry);
2098
+ }
2099
+ }
2100
+ return [left, right];
2101
+ }
1982
2102
  function HelpOverlay() {
1983
2103
  const sections = helpSections();
1984
- const keyWidth = Math.max(...sections.flatMap((s2) => s2.entries.map((e) => e.keys.length)), 0);
2104
+ const otherSections = sections.filter((s2) => s2.title !== "Tool Colors");
2105
+ const otherKeyWidth = Math.max(
2106
+ ...otherSections.flatMap((s2) => s2.entries.map((e) => e.keys.length)),
2107
+ 0
2108
+ );
2109
+ const toolSection = sections.find((s2) => s2.title === "Tool Colors");
2110
+ const toolKeyWidth = toolSection ? Math.max(...toolSection.entries.map((e) => e.keys.length), 0) : 0;
1985
2111
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
1986
2112
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1987
2113
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: "Keyboard shortcuts" }),
1988
2114
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 Esc to close" })
1989
2115
  ] }),
1990
- sections.map((sec) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1991
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.brand, children: sec.title }),
1992
- sec.entries.map((e, i) => /* @__PURE__ */ jsxs(
1993
- Box,
1994
- {
1995
- flexDirection: "row",
1996
- children: [
1997
- /* @__PURE__ */ jsx(Text, { color: theme.accent, children: e.keys.padEnd(keyWidth + 2) }),
1998
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: e.desc })
1999
- ]
2000
- },
2001
- i
2002
- ))
2003
- ] }, sec.title))
2116
+ sections.map((sec) => {
2117
+ if (sec.title === "Tool Colors") {
2118
+ const [leftCol, rightCol] = splitIntoColumns(sec.entries);
2119
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
2120
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.brand, children: sec.title }),
2121
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
2122
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: leftCol.map((e, i) => /* @__PURE__ */ jsxs(
2123
+ Box,
2124
+ {
2125
+ flexDirection: "row",
2126
+ children: [
2127
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: e.keys.padEnd(toolKeyWidth + 2) }),
2128
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: e.desc })
2129
+ ]
2130
+ },
2131
+ i
2132
+ )) }),
2133
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rightCol.map((e, i) => /* @__PURE__ */ jsxs(
2134
+ Box,
2135
+ {
2136
+ flexDirection: "row",
2137
+ children: [
2138
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: e.keys.padEnd(toolKeyWidth + 2) }),
2139
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: e.desc })
2140
+ ]
2141
+ },
2142
+ i
2143
+ )) })
2144
+ ] })
2145
+ ] }, sec.title);
2146
+ }
2147
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
2148
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.brand, children: sec.title }),
2149
+ sec.entries.map((e, i) => /* @__PURE__ */ jsxs(
2150
+ Box,
2151
+ {
2152
+ flexDirection: "row",
2153
+ children: [
2154
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: e.keys.padEnd(otherKeyWidth + 2) }),
2155
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: e.desc })
2156
+ ]
2157
+ },
2158
+ i
2159
+ ))
2160
+ ] }, sec.title);
2161
+ })
2004
2162
  ] });
2005
2163
  }
2006
2164
 
@@ -3525,6 +3683,7 @@ var ToolStreamBox = React5.memo(function ToolStreamBox2({
3525
3683
  startedAt,
3526
3684
  termWidth
3527
3685
  }) {
3686
+ const { glyph, color } = getToolVisual(name);
3528
3687
  const [tick, setTick] = useState(0);
3529
3688
  useEffect(() => {
3530
3689
  const t = setInterval(() => setTick((n) => n + 1), 500);
@@ -3537,8 +3696,11 @@ var ToolStreamBox = React5.memo(function ToolStreamBox2({
3537
3696
  const rows = streamBoxRows(text, MAX_STREAM_LINES, contentWidth);
3538
3697
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
3539
3698
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
3540
- /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u25C6 " }),
3541
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: name }),
3699
+ /* @__PURE__ */ jsxs(Text, { color, children: [
3700
+ glyph,
3701
+ " "
3702
+ ] }),
3703
+ /* @__PURE__ */ jsx(Text, { bold: true, color, children: name }),
3542
3704
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
3543
3705
  hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
3544
3706
  ] }),
@@ -3811,7 +3973,7 @@ function Banner({
3811
3973
  entry
3812
3974
  }) {
3813
3975
  const cwdShort = shortenPath(entry.cwd, 48);
3814
- const projectLabel = path4.basename(entry.cwd);
3976
+ const projectLabel = path5.basename(entry.cwd);
3815
3977
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
3816
3978
  /* @__PURE__ */ jsxs(Text, { children: [
3817
3979
  /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
@@ -4093,6 +4255,7 @@ var Entry = React5.memo(function Entry2({
4093
4255
  ] });
4094
4256
  }
4095
4257
  case "tool": {
4258
+ const { glyph, color } = getToolVisual(entry.name);
4096
4259
  const argSummary = formatToolArgs(entry.name, entry.input);
4097
4260
  const outLines = formatToolOutput(
4098
4261
  entry.name,
@@ -4118,9 +4281,9 @@ var Entry = React5.memo(function Entry2({
4118
4281
  })();
4119
4282
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
4120
4283
  /* @__PURE__ */ jsxs(Text, { children: [
4121
- /* @__PURE__ */ jsx(Text, { color: entry.ok ? theme.success : theme.error, children: entry.ok ? "\u25CF" : "\u2717" }),
4122
- " ",
4123
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: entry.name }),
4284
+ /* @__PURE__ */ jsx(Text, { color, children: glyph }),
4285
+ /* @__PURE__ */ jsx(Text, { children: " " }),
4286
+ /* @__PURE__ */ jsx(Text, { bold: true, color, children: entry.name }),
4124
4287
  argSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
4125
4288
  /* @__PURE__ */ jsx(Text, { children: " " }),
4126
4289
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
@@ -4387,7 +4550,7 @@ function ScrollableHistory({
4387
4550
  }
4388
4551
  var MB = 1024 * 1024;
4389
4552
  function defaultHeapLogPath() {
4390
- return path4.join(wstackGlobalRoot(), "logs", "heap.jsonl");
4553
+ return path5.join(wstackGlobalRoot(), "logs", "heap.jsonl");
4391
4554
  }
4392
4555
  function takeHeapSample() {
4393
4556
  const m = process.memoryUsage();
@@ -4417,10 +4580,10 @@ function startHeapWatchdog(opts = {}) {
4417
4580
  const append = (line) => {
4418
4581
  writeChain = writeChain.then(async () => {
4419
4582
  if (!dirReady) {
4420
- await fs2.mkdir(path4.dirname(logPath), { recursive: true });
4583
+ await fs3.mkdir(path5.dirname(logPath), { recursive: true });
4421
4584
  dirReady = true;
4422
4585
  }
4423
- await fs2.appendFile(logPath, `${line}
4586
+ await fs3.appendFile(logPath, `${line}
4424
4587
  `, "utf8");
4425
4588
  }).catch(() => void 0);
4426
4589
  };
@@ -4593,6 +4756,52 @@ function layoutInputRows(prompt, value, cursor, width) {
4593
4756
  if (row.length > 0 || rows.length === 0) rows.push(row);
4594
4757
  return rows;
4595
4758
  }
4759
+ function inputIndexAtRowCol(prompt, value, width, row, col) {
4760
+ const w = Math.max(1, Math.floor(width));
4761
+ const flat = [];
4762
+ for (let i = 0; i < prompt.length; i++) flat.push({ ch: prompt[i], buf: -1 });
4763
+ for (let i = 0; i < value.length; i++) flat.push({ ch: value[i], buf: i });
4764
+ const rows = [];
4765
+ const starts = [];
4766
+ let cur = [];
4767
+ let curStart = 0;
4768
+ for (const cell of flat) {
4769
+ if (cell.ch === "\n") {
4770
+ rows.push(cur);
4771
+ starts.push(curStart);
4772
+ cur = [];
4773
+ curStart = cell.buf + 1;
4774
+ continue;
4775
+ }
4776
+ if (cur.length === 0 && cell.buf >= 0) curStart = cell.buf;
4777
+ cur.push({ buf: cell.buf });
4778
+ if (cur.length >= w) {
4779
+ rows.push(cur);
4780
+ starts.push(curStart);
4781
+ cur = [];
4782
+ curStart = -1;
4783
+ }
4784
+ }
4785
+ if (cur.length > 0 || rows.length === 0) {
4786
+ rows.push(cur);
4787
+ starts.push(curStart);
4788
+ }
4789
+ const clamp = (n) => Math.max(0, Math.min(value.length, n));
4790
+ if (row < 0) return 0;
4791
+ if (row >= rows.length) return value.length;
4792
+ const r = rows[row];
4793
+ const c = Math.max(0, col);
4794
+ if (c < r.length) {
4795
+ const b = r[c].buf;
4796
+ return b < 0 ? 0 : clamp(b);
4797
+ }
4798
+ for (let k = r.length - 1; k >= 0; k--) {
4799
+ const b = r[k].buf;
4800
+ if (b >= 0) return clamp(b + 1);
4801
+ }
4802
+ const start = starts[row];
4803
+ return clamp(start !== void 0 && start >= 0 ? start : value.length);
4804
+ }
4596
4805
 
4597
4806
  // src/mouse.ts
4598
4807
  var ESC = String.fromCharCode(27);
@@ -5294,7 +5503,25 @@ function ProcessListMonitor() {
5294
5503
  ] })
5295
5504
  ] });
5296
5505
  }
5297
- function GoalPanel({ goal }) {
5506
+ function GoalPanel({
5507
+ goal,
5508
+ onCoordinatorStart,
5509
+ onCoordinatorStop,
5510
+ coordinatorRunning
5511
+ }) {
5512
+ useInput((input) => {
5513
+ if (input === "c" || input === "C") {
5514
+ if (onCoordinatorStart && goal) {
5515
+ const goalText = goal.refinedGoal || goal.goal;
5516
+ onCoordinatorStart(goalText);
5517
+ }
5518
+ }
5519
+ if (input === "S") {
5520
+ if (onCoordinatorStop) {
5521
+ onCoordinatorStop();
5522
+ }
5523
+ }
5524
+ });
5298
5525
  if (!goal) {
5299
5526
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
5300
5527
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: "\u{1F3AF} Goal" }) }),
@@ -5306,7 +5533,11 @@ function GoalPanel({ goal }) {
5306
5533
  ] }),
5307
5534
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " to create one." })
5308
5535
  ] }),
5309
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press F9 to close." }) })
5536
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press F9 to close." }) }),
5537
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: coordinatorRunning ? /* @__PURE__ */ jsxs(Fragment, { children: [
5538
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF Coordinator running " }),
5539
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[S] Stop coordinator" })
5540
+ ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[C] Start coordinator" }) }) })
5310
5541
  ] });
5311
5542
  }
5312
5543
  const displayGoal = goal.refinedGoal || goal.goal;
@@ -5372,7 +5603,11 @@ function GoalPanel({ goal }) {
5372
5603
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Last task: " }),
5373
5604
  /* @__PURE__ */ jsx(Text, { children: goal.lastTask.length > 50 ? goal.lastTask.slice(0, 47) + "\u2026" : goal.lastTask })
5374
5605
  ] }),
5375
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press F9 to close." }) })
5606
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press F9 to close." }) }),
5607
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: coordinatorRunning ? /* @__PURE__ */ jsxs(Fragment, { children: [
5608
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF Coordinator running " }),
5609
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[S] Stop coordinator" })
5610
+ ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[C] Start coordinator" }) }) })
5376
5611
  ] })
5377
5612
  ] });
5378
5613
  }
@@ -5397,6 +5632,152 @@ function renderProgressBar(progress, trend) {
5397
5632
  ] })
5398
5633
  ] });
5399
5634
  }
5635
+ var STATUS_COLOR = {
5636
+ open: "gray",
5637
+ in_progress: "yellow",
5638
+ done: "green"
5639
+ };
5640
+ var STATUS_ICON = {
5641
+ open: "\u25CB",
5642
+ in_progress: "\u25D0",
5643
+ done: "\u2713"
5644
+ };
5645
+ function planFilePath(projectRoot, sessionId, scope) {
5646
+ const base = resolveWstackPaths$1({ projectRoot }).projectSessions;
5647
+ if (scope === "project") {
5648
+ return path5.join(base, "backlog.plan.json");
5649
+ }
5650
+ if (sessionId) {
5651
+ return path5.join(base, `${sessionId}.plan.json`);
5652
+ }
5653
+ return path5.join(base, "backlog.plan.json");
5654
+ }
5655
+ function PlanPanel({ projectRoot, sessionId, onClose: _onClose }) {
5656
+ const [items, setItems] = useState([]);
5657
+ const [title, setTitle2] = useState(void 0);
5658
+ const [scope, setScope] = useState("session");
5659
+ const [loading, setLoading] = useState(true);
5660
+ const [error, setError] = useState(null);
5661
+ async function load(scope_) {
5662
+ setLoading(true);
5663
+ setError(null);
5664
+ try {
5665
+ const filePath = planFilePath(projectRoot, sessionId, scope_);
5666
+ let content;
5667
+ try {
5668
+ content = await fs3.readFile(filePath, "utf-8");
5669
+ } catch {
5670
+ setItems([]);
5671
+ setTitle2(void 0);
5672
+ setScope(scope_);
5673
+ setLoading(false);
5674
+ return;
5675
+ }
5676
+ const parsed = JSON.parse(content);
5677
+ setItems(parsed.items ?? []);
5678
+ setTitle2(parsed.title);
5679
+ setScope(scope_);
5680
+ } catch (err) {
5681
+ setError(err instanceof Error ? err.message : String(err));
5682
+ } finally {
5683
+ setLoading(false);
5684
+ }
5685
+ }
5686
+ useEffect(() => {
5687
+ void load("session");
5688
+ }, []);
5689
+ async function handleScopeSwitch(newScope) {
5690
+ await load(newScope);
5691
+ }
5692
+ useInput((input) => {
5693
+ if (input === "s" || input === "S") {
5694
+ void handleScopeSwitch(scope === "session" ? "project" : "session");
5695
+ }
5696
+ });
5697
+ const open = items.filter((i) => i.status === "open");
5698
+ const inProgress = items.filter((i) => i.status === "in_progress");
5699
+ const done = items.filter((i) => i.status === "done");
5700
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
5701
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 1, children: [
5702
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u{1F4CB} PLAN" }),
5703
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
5704
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: scope === "session" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "session-scoped" }) : /* @__PURE__ */ jsx(Text, { color: "yellow", children: "project-scoped" }) }),
5705
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
5706
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5707
+ items.length,
5708
+ " item",
5709
+ items.length !== 1 ? "s" : ""
5710
+ ] }),
5711
+ title ? /* @__PURE__ */ jsxs(Fragment, { children: [
5712
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
5713
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: title })
5714
+ ] }) : null,
5715
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 F5/Esc to close" })
5716
+ ] }),
5717
+ loading ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading plan..." }) : error ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
5718
+ "Error: ",
5719
+ error
5720
+ ] }) : items.length === 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 0, children: [
5721
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No plan items yet." }),
5722
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5723
+ "Use ",
5724
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: "/plan add <title>" }),
5725
+ " to create one."
5726
+ ] })
5727
+ ] }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 0, children: [
5728
+ inProgress.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
5729
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", bold: true, children: [
5730
+ "In Progress (",
5731
+ inProgress.length,
5732
+ ")"
5733
+ ] }),
5734
+ inProgress.map((item) => /* @__PURE__ */ jsx(PlanRow, { item }, item.id))
5735
+ ] }),
5736
+ open.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
5737
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, bold: true, children: [
5738
+ "Open (",
5739
+ open.length,
5740
+ ")"
5741
+ ] }),
5742
+ open.map((item) => /* @__PURE__ */ jsx(PlanRow, { item }, item.id))
5743
+ ] }),
5744
+ done.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
5745
+ /* @__PURE__ */ jsxs(Text, { color: "green", bold: true, children: [
5746
+ "Done (",
5747
+ done.length,
5748
+ ")"
5749
+ ] }),
5750
+ done.map((item) => /* @__PURE__ */ jsx(PlanRow, { item }, item.id))
5751
+ ] })
5752
+ ] }),
5753
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5754
+ "[",
5755
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "S" }),
5756
+ "] Switch scope to",
5757
+ " ",
5758
+ scope === "session" ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "project" }) : /* @__PURE__ */ jsx(Text, { color: "cyan", children: "session" }),
5759
+ " ",
5760
+ "\xB7 Use ",
5761
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: "/plan add" }),
5762
+ " to add items \xB7",
5763
+ " ",
5764
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "F5/Esc to close" })
5765
+ ] }) })
5766
+ ] });
5767
+ }
5768
+ function PlanRow({ item }) {
5769
+ const icon = STATUS_ICON[item.status] ?? "?";
5770
+ const color = STATUS_COLOR[item.status] ?? "white";
5771
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
5772
+ /* @__PURE__ */ jsx(Text, { color, children: icon }),
5773
+ /* @__PURE__ */ jsx(Text, { color, children: item.title }),
5774
+ item.details ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5775
+ " \u2014 ",
5776
+ item.details.slice(0, 60),
5777
+ item.details.length > 60 ? "\u2026" : ""
5778
+ ] }) : null
5779
+ ] });
5780
+ }
5400
5781
  var KIND_COLOR = {
5401
5782
  goal: "cyan",
5402
5783
  task: "yellow",
@@ -5709,6 +6090,14 @@ var MAX_ITERATIONS_PRESETS = [100, 200, 500, 1e3, 0];
5709
6090
  var AUTO_PROCEED_MAX_PRESETS = [10, 25, 50, 100, 250, 0];
5710
6091
  var ENHANCE_DELAY_PRESETS = [3e4, 45e3, 6e4, 9e4, 12e4];
5711
6092
  var ENHANCE_LANGUAGES = ["original", "english"];
6093
+ var TOKEN_SAVING_TIERS = ["off", "minimal", "light", "medium", "aggressive"];
6094
+ var TOKEN_SAVING_TIER_DESCS = {
6095
+ off: "All tools enabled (full prompt)",
6096
+ minimal: "~3\u20134k tokens \u2014 core tools only",
6097
+ light: "~2\u20133k tokens \u2014 core + patterns",
6098
+ medium: "~1.5\u20132k tokens \u2014 most tools enabled",
6099
+ aggressive: "~4\u20135k tokens \u2014 trimmed prompt"
6100
+ };
5712
6101
  function formatSettingsDelay(ms) {
5713
6102
  if (ms === 0) return "disabled";
5714
6103
  if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
@@ -5743,7 +6132,7 @@ function SettingsPicker({
5743
6132
  featureMemory,
5744
6133
  featureSkills,
5745
6134
  featureModelsRegistry,
5746
- featureTokenSaving,
6135
+ tokenSavingTier,
5747
6136
  allowOutsideProjectRoot,
5748
6137
  contextAutoCompact,
5749
6138
  contextStrategy,
@@ -5830,8 +6219,8 @@ function SettingsPicker({
5830
6219
  },
5831
6220
  {
5832
6221
  label: "Token-saving mode",
5833
- value: boolVal(featureTokenSaving),
5834
- detail: "Omit non-essential tools and trim system prompt to save tokens"
6222
+ value: tokenSavingTier,
6223
+ detail: TOKEN_SAVING_TIER_DESCS[tokenSavingTier]
5835
6224
  },
5836
6225
  {
5837
6226
  label: "Allow outside project",
@@ -6448,10 +6837,10 @@ async function loadIndex(root) {
6448
6837
  async function walk(root, rel, depth, out) {
6449
6838
  if (out.length >= MAX_FILES_INDEXED) return;
6450
6839
  if (depth > MAX_DEPTH) return;
6451
- const dir = rel ? path4.join(root, rel) : root;
6840
+ const dir = rel ? path5.join(root, rel) : root;
6452
6841
  let entries;
6453
6842
  try {
6454
- entries = await fs2.readdir(dir, { withFileTypes: true });
6843
+ entries = await fs3.readdir(dir, { withFileTypes: true });
6455
6844
  } catch {
6456
6845
  return;
6457
6846
  }
@@ -8055,7 +8444,7 @@ function reducer(state, action) {
8055
8444
  featureMemory: action.featureMemory,
8056
8445
  featureSkills: action.featureSkills,
8057
8446
  featureModelsRegistry: action.featureModelsRegistry,
8058
- featureTokenSaving: action.featureTokenSaving,
8447
+ tokenSavingTier: action.tokenSavingTier,
8059
8448
  allowOutsideProjectRoot: action.allowOutsideProjectRoot,
8060
8449
  contextAutoCompact: action.contextAutoCompact,
8061
8450
  contextStrategy: action.contextStrategy,
@@ -8115,7 +8504,12 @@ function reducer(state, action) {
8115
8504
  if (f === 10) return { ...state, settingsPicker: { ...sp, featureMemory: !sp.featureMemory, hint: bootHint } };
8116
8505
  if (f === 11) return { ...state, settingsPicker: { ...sp, featureSkills: !sp.featureSkills, hint: bootHint } };
8117
8506
  if (f === 12) return { ...state, settingsPicker: { ...sp, featureModelsRegistry: !sp.featureModelsRegistry, hint: bootHint } };
8118
- if (f === 13) return { ...state, settingsPicker: { ...sp, featureTokenSaving: !sp.featureTokenSaving, hint: bootHint } };
8507
+ if (f === 13) {
8508
+ const i = TOKEN_SAVING_TIERS.indexOf(sp.tokenSavingTier);
8509
+ const base = i < 0 ? 0 : i;
8510
+ const next = (base + action.delta + TOKEN_SAVING_TIERS.length) % TOKEN_SAVING_TIERS.length;
8511
+ return { ...state, settingsPicker: { ...sp, tokenSavingTier: TOKEN_SAVING_TIERS[next] ?? "off", hint: bootHint } };
8512
+ }
8119
8513
  if (f === 14) return { ...state, settingsPicker: { ...sp, allowOutsideProjectRoot: !sp.allowOutsideProjectRoot, hint: void 0 } };
8120
8514
  if (f === 15) return { ...state, settingsPicker: { ...sp, contextAutoCompact: !sp.contextAutoCompact, hint: void 0 } };
8121
8515
  if (f === 16) {
@@ -8574,6 +8968,10 @@ function reducer(state, action) {
8574
8968
  const opening = !state.processListOpen;
8575
8969
  return opening ? { ...state, processListOpen: true, monitorOpen: false, agentsMonitorOpen: false, helpOpen: false, todosMonitorOpen: false, queuePanelOpen: false, goalPanelOpen: false } : { ...state, processListOpen: false };
8576
8970
  }
8971
+ case "togglePlanPanel": {
8972
+ const opening = !state.planPanelOpen;
8973
+ return opening ? { ...state, planPanelOpen: true, monitorOpen: false, agentsMonitorOpen: false, helpOpen: false, todosMonitorOpen: false, queuePanelOpen: false, processListOpen: false, goalPanelOpen: false } : { ...state, planPanelOpen: false };
8974
+ }
8577
8975
  case "toggleGoalPanel": {
8578
8976
  const opening = !state.goalPanelOpen;
8579
8977
  return opening ? { ...state, goalPanelOpen: true, monitorOpen: false, agentsMonitorOpen: false, helpOpen: false, todosMonitorOpen: false, queuePanelOpen: false, processListOpen: false } : { ...state, goalPanelOpen: false };
@@ -9118,7 +9516,11 @@ function App({
9118
9516
  getLiveSessions,
9119
9517
  onSwitchToSession,
9120
9518
  initialAgentsMonitorOpen,
9121
- subscribeCoordinatorEvents
9519
+ subscribeCoordinatorEvents,
9520
+ onCoordinatorStart,
9521
+ onCoordinatorStop,
9522
+ coordinatorRunning = false,
9523
+ clientId
9122
9524
  }) {
9123
9525
  const { exit } = useApp();
9124
9526
  const { stdout } = useStdout();
@@ -9154,6 +9556,28 @@ function App({
9154
9556
  setIndexState(getIndexState());
9155
9557
  return onIndexStateChange((next) => setIndexState(next));
9156
9558
  }, []);
9559
+ const [breakerCountdown, setBreakerCountdown] = useState(
9560
+ () => getProcessRegistry().getBreakerCountdown()
9561
+ );
9562
+ useEffect(() => {
9563
+ const s2 = getSettings?.();
9564
+ if (s2) {
9565
+ getProcessRegistry().setBreakerConfig({
9566
+ enabled: s2.breakerEnabled ?? false,
9567
+ autoKillResetMs: s2.breakerAutoKillResetMs ?? 6e4
9568
+ });
9569
+ }
9570
+ return getProcessRegistry().onBreakerCountdownChange((snap) => setBreakerCountdown(snap));
9571
+ }, [getSettings]);
9572
+ const breakerArmed = breakerCountdown !== null;
9573
+ useEffect(() => {
9574
+ if (!breakerArmed) return;
9575
+ const t = setInterval(
9576
+ () => setBreakerCountdown(getProcessRegistry().getBreakerCountdown()),
9577
+ 1e3
9578
+ );
9579
+ return () => clearInterval(t);
9580
+ }, [breakerArmed]);
9157
9581
  useEffect(() => {
9158
9582
  setHiddenItems([...statuslineHiddenItems]);
9159
9583
  }, [statuslineHiddenItems]);
@@ -9251,7 +9675,7 @@ function App({
9251
9675
  },
9252
9676
  autonomyPicker: { open: false, options: [], selected: 0 },
9253
9677
  resumePicker: { open: false, sessions: [], selected: 0, busy: false, hint: void 0, error: void 0 },
9254
- 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, allowOutsideProjectRoot: 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" },
9678
+ 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, tokenSavingTier: "off", allowOutsideProjectRoot: 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" },
9255
9679
  projectPicker: { open: false, allItems: [], items: [], selected: 0, filter: "", hint: void 0 },
9256
9680
  confirmQueue: [],
9257
9681
  enhance: null,
@@ -9279,6 +9703,7 @@ function App({
9279
9703
  todosMonitorOpen: false,
9280
9704
  queuePanelOpen: false,
9281
9705
  processListOpen: false,
9706
+ planPanelOpen: false,
9282
9707
  goalPanelOpen: false,
9283
9708
  sessionsPanelOpen: false,
9284
9709
  sessionsPanel: { sessions: [], busy: false, selected: -1 },
@@ -9318,20 +9743,20 @@ function App({
9318
9743
  const lastEnterAtRef = useRef(0);
9319
9744
  const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
9320
9745
  const projectName = React5.useMemo(() => {
9321
- const base = path4.basename(projectRoot);
9322
- return base && base !== path4.sep ? base : void 0;
9746
+ const base = path5.basename(projectRoot);
9747
+ return base && base !== path5.sep ? base : void 0;
9323
9748
  }, [projectRoot]);
9324
9749
  const [workingDirChip, setWorkingDirChip] = React5.useState(() => {
9325
9750
  const ctx = agent.ctx;
9326
9751
  if (ctx.workingDir && ctx.workingDir !== projectRoot) {
9327
- return path4.relative(projectRoot, ctx.workingDir) || ".";
9752
+ return path5.relative(projectRoot, ctx.workingDir) || ".";
9328
9753
  }
9329
9754
  return void 0;
9330
9755
  });
9331
9756
  React5.useEffect(() => {
9332
9757
  const ctx = agent.ctx;
9333
9758
  return ctx.onWorkingDirChanged((newDir) => {
9334
- const rel = path4.relative(projectRoot, newDir) || ".";
9759
+ const rel = path5.relative(projectRoot, newDir) || ".";
9335
9760
  setWorkingDirChip(rel === "." ? void 0 : rel);
9336
9761
  });
9337
9762
  }, [agent.ctx, projectRoot]);
@@ -9728,7 +10153,7 @@ function App({
9728
10153
  let cancelled = false;
9729
10154
  const poll = async () => {
9730
10155
  try {
9731
- const data = await fs2.readFile(planPath, "utf8");
10156
+ const data = await fs3.readFile(planPath, "utf8");
9732
10157
  const parsed = JSON.parse(data);
9733
10158
  if (cancelled) return;
9734
10159
  if (!Array.isArray(parsed.items)) {
@@ -9762,7 +10187,7 @@ function App({
9762
10187
  let cancelled = false;
9763
10188
  const poll = async () => {
9764
10189
  try {
9765
- const data = await fs2.readFile(taskPath, "utf8");
10190
+ const data = await fs3.readFile(taskPath, "utf8");
9766
10191
  const parsed = JSON.parse(data);
9767
10192
  if (cancelled) return;
9768
10193
  if (!Array.isArray(parsed.tasks)) {
@@ -9874,6 +10299,7 @@ function App({
9874
10299
  if (stateRef.current.todosMonitorOpen) dispatch({ type: "toggleTodosMonitor" });
9875
10300
  if (stateRef.current.queuePanelOpen) dispatch({ type: "toggleQueuePanel" });
9876
10301
  if (stateRef.current.processListOpen) dispatch({ type: "toggleProcessList" });
10302
+ if (stateRef.current.planPanelOpen) dispatch({ type: "togglePlanPanel" });
9877
10303
  if (stateRef.current.goalPanelOpen) dispatch({ type: "toggleGoalPanel" });
9878
10304
  if (stateRef.current.sessionsPanelOpen) dispatch({ type: "toggleSessionsPanel" });
9879
10305
  eraseLiveRegion();
@@ -9899,7 +10325,7 @@ function App({
9899
10325
  featureMemory: sp.featureMemory,
9900
10326
  featureSkills: sp.featureSkills,
9901
10327
  featureModelsRegistry: sp.featureModelsRegistry,
9902
- featureTokenSaving: sp.featureTokenSaving,
10328
+ tokenSavingTier: sp.tokenSavingTier,
9903
10329
  allowOutsideProjectRoot: sp.allowOutsideProjectRoot,
9904
10330
  contextAutoCompact: sp.contextAutoCompact,
9905
10331
  contextStrategy: sp.contextStrategy,
@@ -10061,9 +10487,9 @@ function App({
10061
10487
  dispatch({ type: "pickerClose" });
10062
10488
  return;
10063
10489
  }
10064
- const absPath = path4.isAbsolute(picked) ? picked : path4.join(projectRoot, picked);
10490
+ const absPath = path5.isAbsolute(picked) ? picked : path5.join(projectRoot, picked);
10065
10491
  try {
10066
- const data = await fs2.readFile(absPath, "utf8");
10492
+ const data = await fs3.readFile(absPath, "utf8");
10067
10493
  const token = await builder.registerFile({
10068
10494
  kind: "file",
10069
10495
  data,
@@ -10303,7 +10729,7 @@ function App({
10303
10729
  featureMemory: s2.featureMemory ?? true,
10304
10730
  featureSkills: s2.featureSkills ?? true,
10305
10731
  featureModelsRegistry: s2.featureModelsRegistry ?? true,
10306
- featureTokenSaving: s2.featureTokenSaving ?? false,
10732
+ tokenSavingTier: s2.featureTokenSaving ?? "off",
10307
10733
  allowOutsideProjectRoot: s2.allowOutsideProjectRoot ?? true,
10308
10734
  contextAutoCompact: s2.contextAutoCompact ?? true,
10309
10735
  contextStrategy: s2.contextStrategy ?? "hybrid",
@@ -10439,7 +10865,7 @@ function App({
10439
10865
  featureMemory: sp.featureMemory,
10440
10866
  featureSkills: sp.featureSkills,
10441
10867
  featureModelsRegistry: sp.featureModelsRegistry,
10442
- featureTokenSaving: sp.featureTokenSaving,
10868
+ featureTokenSaving: sp.tokenSavingTier,
10443
10869
  allowOutsideProjectRoot: sp.allowOutsideProjectRoot,
10444
10870
  contextAutoCompact: sp.contextAutoCompact,
10445
10871
  contextStrategy: sp.contextStrategy,
@@ -10471,7 +10897,7 @@ function App({
10471
10897
  state.settingsPicker.featureMemory,
10472
10898
  state.settingsPicker.featureSkills,
10473
10899
  state.settingsPicker.featureModelsRegistry,
10474
- state.settingsPicker.featureTokenSaving,
10900
+ state.settingsPicker.tokenSavingTier,
10475
10901
  state.settingsPicker.allowOutsideProjectRoot,
10476
10902
  state.settingsPicker.contextAutoCompact,
10477
10903
  state.settingsPicker.contextStrategy,
@@ -10746,6 +11172,47 @@ function App({
10746
11172
  if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
10747
11173
  };
10748
11174
  }, [events, agent.ctx.todos]);
11175
+ useEffect(() => {
11176
+ if (!clientId || !events) return;
11177
+ let toolCalls = 0;
11178
+ const emitStatus = () => {
11179
+ const usage = tokenCounter?.total();
11180
+ const cost = tokenCounter?.estimateCost();
11181
+ const mode = getAutonomy?.() ?? "off";
11182
+ events.emit("client.status", {
11183
+ clientType: "tui",
11184
+ clientId,
11185
+ projectHash: agent.ctx.projectRoot ? projectSlug(agent.ctx.projectRoot) : "unknown",
11186
+ agentCount: 1,
11187
+ // TUI is a single leader agent
11188
+ model: agent.ctx.model,
11189
+ mode,
11190
+ toolCalls,
11191
+ inputTokens: usage?.input ?? 0,
11192
+ outputTokens: usage?.output ?? 0,
11193
+ cacheTokens: (usage?.cacheRead ?? 0) + (usage?.cacheWrite ?? 0),
11194
+ costUsd: cost?.total ?? 0,
11195
+ timestamp: Date.now(),
11196
+ projectSlug: agent.ctx.projectRoot ? projectSlug(agent.ctx.projectRoot) : "unknown"
11197
+ });
11198
+ };
11199
+ const offTool = events.on("tool.executed", () => {
11200
+ toolCalls++;
11201
+ emitStatus();
11202
+ });
11203
+ const offProviderResp = events.on("provider.response", () => {
11204
+ emitStatus();
11205
+ });
11206
+ const offIterCompleted = events.on("iteration.completed", () => {
11207
+ emitStatus();
11208
+ });
11209
+ emitStatus();
11210
+ return () => {
11211
+ offTool();
11212
+ offProviderResp();
11213
+ offIterCompleted();
11214
+ };
11215
+ }, [events, clientId, tokenCounter, getAutonomy, agent.ctx.model, agent.ctx.projectRoot]);
10749
11216
  useEffect(() => {
10750
11217
  if (!registerDebugStreamCallback) return;
10751
11218
  let cancelled = false;
@@ -11566,6 +12033,21 @@ function App({
11566
12033
  toggleWorktreeOverlay();
11567
12034
  return;
11568
12035
  }
12036
+ if (key.fn === 5) {
12037
+ if (state.planPanelOpen) {
12038
+ dispatch({ type: "togglePlanPanel" });
12039
+ } else {
12040
+ if (state.agentsMonitorOpen) dispatch({ type: "toggleAgentsMonitor" });
12041
+ if (state.monitorOpen) dispatch({ type: "toggleMonitor" });
12042
+ if (state.worktreeMonitorOpen) dispatch({ type: "worktreeMonitorToggle" });
12043
+ if (state.todosMonitorOpen) dispatch({ type: "toggleTodosMonitor" });
12044
+ if (state.autoPhase?.monitorOpen) dispatch({ type: "autoPhaseMonitorToggle" });
12045
+ if (state.settingsPicker.open) dispatch({ type: "settingsClose" });
12046
+ if (state.helpOpen) dispatch({ type: "toggleHelp" });
12047
+ dispatch({ type: "togglePlanPanel" });
12048
+ }
12049
+ return;
12050
+ }
11569
12051
  if (key.fn === 6) {
11570
12052
  toggleTodosOverlay();
11571
12053
  return;
@@ -11668,7 +12150,7 @@ function App({
11668
12150
  featureMemory: cfg.featureMemory ?? true,
11669
12151
  featureSkills: cfg.featureSkills ?? true,
11670
12152
  featureModelsRegistry: cfg.featureModelsRegistry ?? true,
11671
- featureTokenSaving: cfg.featureTokenSaving ?? false,
12153
+ tokenSavingTier: cfg.featureTokenSaving ?? "off",
11672
12154
  allowOutsideProjectRoot: cfg.allowOutsideProjectRoot ?? true,
11673
12155
  contextAutoCompact: cfg.contextAutoCompact ?? true,
11674
12156
  contextStrategy: cfg.contextStrategy ?? "hybrid",
@@ -11851,6 +12333,57 @@ function App({
11851
12333
  setDraft(buffer, buffer.length);
11852
12334
  return;
11853
12335
  }
12336
+ if (key.upArrow || key.downArrow || key.pageUp || key.pageDown) {
12337
+ const width = stdout?.columns ?? 80;
12338
+ const rows = layoutInputRows(INPUT_PROMPT, buffer, cursor, width);
12339
+ if (rows.length <= 1) ; else {
12340
+ let row = 0, col = 0, offset = 0;
12341
+ outer: for (let r = 0; r < rows.length; r++) {
12342
+ const cells = rows[r];
12343
+ for (let c = 0; c < cells.length; c++) {
12344
+ if (offset === cursor) {
12345
+ row = r;
12346
+ col = c;
12347
+ break outer;
12348
+ }
12349
+ offset++;
12350
+ }
12351
+ if (cells.length < width) offset++;
12352
+ }
12353
+ if (key.upArrow) {
12354
+ if (row > 0) {
12355
+ const prevRowLen = rows[row - 1].filter((cell) => !cell.prompt && !cell.chip).length;
12356
+ const targetCol = Math.min(col, prevRowLen);
12357
+ const target = inputIndexAtRowCol(INPUT_PROMPT, buffer, width, row - 1, targetCol);
12358
+ setDraft(buffer, target);
12359
+ return;
12360
+ }
12361
+ return;
12362
+ }
12363
+ if (key.downArrow) {
12364
+ if (row < rows.length - 1) {
12365
+ const nextRowLen = rows[row + 1].filter((cell) => !cell.prompt && !cell.chip).length;
12366
+ const targetCol = Math.min(col, nextRowLen);
12367
+ const target = inputIndexAtRowCol(INPUT_PROMPT, buffer, width, row + 1, targetCol);
12368
+ setDraft(buffer, target);
12369
+ return;
12370
+ }
12371
+ return;
12372
+ }
12373
+ if (key.pageUp || key.pageDown) {
12374
+ const pageSize = Math.max(1, Math.floor((stdout?.rows ?? 24) / 2));
12375
+ const delta = key.pageUp ? -pageSize : pageSize;
12376
+ const targetRow = Math.max(0, Math.min(rows.length - 1, row + delta));
12377
+ if (targetRow !== row) {
12378
+ const targetRowLen = rows[targetRow].filter((cell) => !cell.prompt && !cell.chip).length;
12379
+ const targetCol = Math.min(col, targetRowLen);
12380
+ const target = inputIndexAtRowCol(INPUT_PROMPT, buffer, width, targetRow, targetCol);
12381
+ setDraft(buffer, target);
12382
+ }
12383
+ return;
12384
+ }
12385
+ }
12386
+ }
11854
12387
  const overlayOpen = state.monitorOpen || state.agentsMonitorOpen || state.worktreeMonitorOpen || state.todosMonitorOpen || state.queuePanelOpen || state.processListOpen || state.goalPanelOpen || state.sessionsPanelOpen || state.coordinator.monitorOpen || state.helpOpen || (state.autoPhase?.monitorOpen ?? false) || state.rewindOverlay !== null;
11855
12388
  if (mouseMode && !overlayOpen) {
11856
12389
  if (key.mouse?.kind === "wheel") {
@@ -11980,7 +12513,7 @@ function App({
11980
12513
  setDraft(next2, cursor);
11981
12514
  return;
11982
12515
  }
11983
- if (key.ctrl && input === "v") {
12516
+ if (key.ctrl) {
11984
12517
  await pasteClipboardText();
11985
12518
  return;
11986
12519
  }
@@ -12626,7 +13159,7 @@ User message:
12626
13159
  featureMemory: state.settingsPicker.featureMemory,
12627
13160
  featureSkills: state.settingsPicker.featureSkills,
12628
13161
  featureModelsRegistry: state.settingsPicker.featureModelsRegistry,
12629
- featureTokenSaving: state.settingsPicker.featureTokenSaving,
13162
+ tokenSavingTier: state.settingsPicker.tokenSavingTier,
12630
13163
  allowOutsideProjectRoot: state.settingsPicker.allowOutsideProjectRoot,
12631
13164
  contextAutoCompact: state.settingsPicker.contextAutoCompact,
12632
13165
  contextStrategy: state.settingsPicker.contextStrategy,
@@ -12811,6 +13344,7 @@ User message:
12811
13344
  eternalStage: state.eternalStage,
12812
13345
  goalSummary: state.goalSummary,
12813
13346
  indexState,
13347
+ breakerCountdown,
12814
13348
  modeLabel: liveModeLabel || void 0,
12815
13349
  debugStreamStats: state.debugStreamStats,
12816
13350
  enhanceCountdown,
@@ -12819,7 +13353,7 @@ User message:
12819
13353
  autoProceedCountdown: state.countdown?.remainingSeconds ?? null,
12820
13354
  sessionCount,
12821
13355
  mailbox: mailboxStatus,
12822
- tokenSavingMode: getSettings ? getSettings().featureTokenSaving : tokenSavingMode,
13356
+ tokenSavingMode: getSettings ? getSettings().featureTokenSaving !== "off" : tokenSavingMode,
12823
13357
  toolCount
12824
13358
  }
12825
13359
  ) }),
@@ -12889,7 +13423,23 @@ User message:
12889
13423
  Object.keys(state.worktrees).length > 0 && !state.worktreeMonitorOpen && !state.monitorOpen ? /* @__PURE__ */ jsx(WorktreePanel, { worktrees: state.worktrees, nowTick }) : null,
12890
13424
  state.queuePanelOpen ? /* @__PURE__ */ jsx(QueuePanel, { items: state.queue }) : null,
12891
13425
  state.processListOpen ? /* @__PURE__ */ jsx(ProcessListMonitor, {}) : null,
12892
- state.goalPanelOpen ? /* @__PURE__ */ jsx(GoalPanel, { goal: state.goalSummary }) : null,
13426
+ state.planPanelOpen ? /* @__PURE__ */ jsx(
13427
+ PlanPanel,
13428
+ {
13429
+ projectRoot,
13430
+ sessionId: agent.ctx.session?.id ?? null,
13431
+ onClose: () => dispatch({ type: "togglePlanPanel" })
13432
+ }
13433
+ ) : null,
13434
+ state.goalPanelOpen ? /* @__PURE__ */ jsx(
13435
+ GoalPanel,
13436
+ {
13437
+ goal: state.goalSummary,
13438
+ onCoordinatorStart: onCoordinatorStart ?? void 0,
13439
+ onCoordinatorStop: onCoordinatorStop ?? void 0,
13440
+ coordinatorRunning
13441
+ }
13442
+ ) : null,
12893
13443
  (() => {
12894
13444
  const anyMonitorOpen = state.agentsMonitorOpen || (state.autoPhase?.monitorOpen ?? false) || state.worktreeMonitorOpen || state.todosMonitorOpen || state.monitorOpen || state.processListOpen || state.queuePanelOpen || state.goalPanelOpen;
12895
13445
  let nextPanelHint;
@@ -13072,7 +13622,7 @@ async function runTui(opts) {
13072
13622
  stdout,
13073
13623
  events: opts.events,
13074
13624
  model: opts.model,
13075
- appName: opts.projectRoot ? path4.basename(opts.projectRoot) : void 0
13625
+ appName: opts.projectRoot ? path5.basename(opts.projectRoot) : void 0
13076
13626
  });
13077
13627
  };
13078
13628
  const stopTitle = () => {
@@ -13142,7 +13692,7 @@ async function runTui(opts) {
13142
13692
  await mailbox.registerClient({
13143
13693
  clientId,
13144
13694
  sessionId: opts.projectRoot,
13145
- name: `TUI [${path4.basename(opts.projectRoot)}]`,
13695
+ name: `TUI [${path5.basename(opts.projectRoot)}]`,
13146
13696
  source: "tui",
13147
13697
  pid: process.pid
13148
13698
  });
@@ -13285,7 +13835,9 @@ async function runTui(opts) {
13285
13835
  getLiveSessions: opts.getLiveSessions,
13286
13836
  onSwitchToSession: opts.onSwitchToSession,
13287
13837
  initialAgentsMonitorOpen: opts.initialAgentsMonitorOpen,
13288
- subscribeCoordinatorEvents: opts.subscribeCoordinatorEvents
13838
+ subscribeCoordinatorEvents: opts.subscribeCoordinatorEvents,
13839
+ onCoordinatorStart: opts.onCoordinatorStart,
13840
+ onCoordinatorStop: opts.onCoordinatorStop
13289
13841
  }),
13290
13842
  { exitOnCtrlC: false, stdin: inkStdin }
13291
13843
  );