@wrongstack/tui 0.260.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';
7
+ import * as fs3 from 'fs/promises';
8
+ import { expectDefined, toErrorMessage, resolveWstackPaths as resolveWstackPaths$1 } from '@wrongstack/core/utils';
8
9
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
9
10
  import { getIndexState, onIndexStateChange, getProcessRegistry } from '@wrongstack/tools';
10
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
- import { readClipboardImage } from '@wrongstack/runtime/clipboard';
12
- import { expectDefined } from '@wrongstack/core/utils';
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
 
@@ -160,11 +161,13 @@ function StatusBar({
160
161
  workingDir,
161
162
  processCount,
162
163
  context,
164
+ contextStrategy,
163
165
  hiddenItems,
164
166
  events,
165
167
  eternalStage,
166
168
  goalSummary,
167
169
  indexState,
170
+ breakerCountdown,
168
171
  modeLabel,
169
172
  debugStreamStats,
170
173
  enhanceCountdown,
@@ -273,7 +276,12 @@ function StatusBar({
273
276
  "ctx ",
274
277
  renderMeter(clampedRatio, 8),
275
278
  " ",
276
- pctText
279
+ pctText,
280
+ contextStrategy ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
281
+ " [",
282
+ contextStrategy,
283
+ "]"
284
+ ] }) : null
277
285
  ] });
278
286
  })()
279
287
  ] }) : null,
@@ -334,7 +342,21 @@ function StatusBar({
334
342
  ] }) : indexState?.circuit?.state === "open" ? /* @__PURE__ */ jsxs(Fragment, { children: [
335
343
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
336
344
  /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2699 index paused (/reindex)" })
337
- ] }) : 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
338
360
  ] })
339
361
  ) }),
340
362
  hasSecondLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
@@ -485,6 +507,11 @@ function StatusBar({
485
507
  plan.done > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
486
508
  "\u2713",
487
509
  plan.done
510
+ ] }) : null,
511
+ plan.scope ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
512
+ " [",
513
+ plan.scope,
514
+ "]"
488
515
  ] }) : null
489
516
  ] })
490
517
  ] }) : null,
@@ -515,6 +542,11 @@ function StatusBar({
515
542
  tasks.failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
516
543
  "\u2717",
517
544
  tasks.failed
545
+ ] }) : null,
546
+ tasks.scope ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
547
+ " [",
548
+ tasks.scope,
549
+ "]"
518
550
  ] }) : null
519
551
  ] })
520
552
  ] }) : null,
@@ -594,7 +626,8 @@ function StatusBar({
594
626
  ] }),
595
627
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
596
628
  " ",
597
- nextStepsAutoSubmitLabel ? formatSuggestionLabel(nextStepsAutoSubmitLabel) : ""
629
+ nextStepsAutoSubmitLabel ? formatSuggestionLabel(nextStepsAutoSubmitLabel) : "",
630
+ " \xB7 \u21E5 edit"
598
631
  ] })
599
632
  ] }) : null
600
633
  ] }) : /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
@@ -1758,8 +1791,8 @@ function FilePicker({ query, matches, selected }) {
1758
1791
  ] }, m))
1759
1792
  ] });
1760
1793
  }
1761
- function highlight(path6, _query) {
1762
- return path6;
1794
+ function highlight(path7, _query) {
1795
+ return path7;
1763
1796
  }
1764
1797
  function FleetPanel({
1765
1798
  entries,
@@ -1923,6 +1956,36 @@ function MailboxPanel({
1923
1956
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/mailbox \u2014 Esc to close" }) })
1924
1957
  ] });
1925
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
+ }
1926
1989
  function helpSections() {
1927
1990
  const nav = [];
1928
1991
  nav.push(
@@ -1969,31 +2032,133 @@ function helpSections() {
1969
2032
  { keys: "/settings", desc: "autonomy defaults (also Ctrl+S)" },
1970
2033
  { keys: "/clear", desc: "clear the conversation" }
1971
2034
  ]
2035
+ },
2036
+ {
2037
+ title: "Tool Colors",
2038
+ entries: toolColorLegend()
1972
2039
  }
1973
2040
  ];
1974
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
+ }
1975
2102
  function HelpOverlay() {
1976
2103
  const sections = helpSections();
1977
- 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;
1978
2111
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
1979
2112
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1980
2113
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: "Keyboard shortcuts" }),
1981
2114
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 Esc to close" })
1982
2115
  ] }),
1983
- sections.map((sec) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1984
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.brand, children: sec.title }),
1985
- sec.entries.map((e, i) => /* @__PURE__ */ jsxs(
1986
- Box,
1987
- {
1988
- flexDirection: "row",
1989
- children: [
1990
- /* @__PURE__ */ jsx(Text, { color: theme.accent, children: e.keys.padEnd(keyWidth + 2) }),
1991
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: e.desc })
1992
- ]
1993
- },
1994
- i
1995
- ))
1996
- ] }, 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
+ })
1997
2162
  ] });
1998
2163
  }
1999
2164
 
@@ -3518,6 +3683,7 @@ var ToolStreamBox = React5.memo(function ToolStreamBox2({
3518
3683
  startedAt,
3519
3684
  termWidth
3520
3685
  }) {
3686
+ const { glyph, color } = getToolVisual(name);
3521
3687
  const [tick, setTick] = useState(0);
3522
3688
  useEffect(() => {
3523
3689
  const t = setInterval(() => setTick((n) => n + 1), 500);
@@ -3530,8 +3696,11 @@ var ToolStreamBox = React5.memo(function ToolStreamBox2({
3530
3696
  const rows = streamBoxRows(text, MAX_STREAM_LINES, contentWidth);
3531
3697
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
3532
3698
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
3533
- /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u25C6 " }),
3534
- /* @__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 }),
3535
3704
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
3536
3705
  hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
3537
3706
  ] }),
@@ -3804,7 +3973,7 @@ function Banner({
3804
3973
  entry
3805
3974
  }) {
3806
3975
  const cwdShort = shortenPath(entry.cwd, 48);
3807
- const projectLabel = path4.basename(entry.cwd);
3976
+ const projectLabel = path5.basename(entry.cwd);
3808
3977
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
3809
3978
  /* @__PURE__ */ jsxs(Text, { children: [
3810
3979
  /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
@@ -3922,13 +4091,14 @@ function parseWithHeading(content, strict) {
3922
4091
  if (steps.length === 0) {
3923
4092
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
3924
4093
  }
3925
- if (strict && !afterHeading.includes("</next_steps>")) {
4094
+ const headingWasXmlTag = headingMatch[0].startsWith("<");
4095
+ if (strict && headingWasXmlTag && !afterHeading.includes("</next_steps>")) {
3926
4096
  return { steps: [], texts: [], stripped: content, autoTexts: [] };
3927
4097
  }
3928
4098
  const texts = steps.map((s2) => s2.text);
3929
4099
  const autoTexts = steps.filter((s2) => s2.auto).map((s2) => s2.text);
3930
4100
  const blockStart = headingMatch.index;
3931
- const blockEnd = headingEnd + findBlockEnd(afterHeading, steps.length);
4101
+ const blockEnd = headingMatch[0].length + findBlockEnd(afterHeading, steps.length);
3932
4102
  const stripped = (content.slice(0, blockStart) + content.slice(blockStart + blockEnd)).replace(/\n{3,}/g, "\n\n").trim();
3933
4103
  return { steps, texts, stripped, autoTexts };
3934
4104
  }
@@ -3937,24 +4107,26 @@ function buildPermissiveHeadingRe() {
3937
4107
  return new RegExp(variants, "i");
3938
4108
  }
3939
4109
  function findBlockEnd(afterHeading, stepCount) {
4110
+ const closeIdx = afterHeading.indexOf("</next_steps>");
4111
+ if (closeIdx !== -1) {
4112
+ let end = closeIdx + "</next_steps>".length;
4113
+ if (afterHeading[end] === "\n") end += 1;
4114
+ return end;
4115
+ }
3940
4116
  const lines = afterHeading.split("\n");
3941
4117
  let consumed = 0;
3942
4118
  let found = 0;
3943
4119
  for (const rawLine of lines) {
3944
4120
  const line = rawLine.trim();
3945
- if (line === "</next_steps>") {
3946
- consumed += rawLine.length + 1;
3947
- break;
3948
- }
3949
- if (!line) {
3950
- consumed += rawLine.length + 1;
3951
- continue;
3952
- }
4121
+ if (!line) break;
3953
4122
  const m = ITEM_RE.exec(line);
3954
4123
  if (!m) break;
3955
4124
  consumed += rawLine.length + 1;
3956
4125
  found++;
3957
- if (found >= stepCount) break;
4126
+ if (found >= stepCount) {
4127
+ consumed -= 1;
4128
+ break;
4129
+ }
3958
4130
  }
3959
4131
  return consumed;
3960
4132
  }
@@ -4083,6 +4255,7 @@ var Entry = React5.memo(function Entry2({
4083
4255
  ] });
4084
4256
  }
4085
4257
  case "tool": {
4258
+ const { glyph, color } = getToolVisual(entry.name);
4086
4259
  const argSummary = formatToolArgs(entry.name, entry.input);
4087
4260
  const outLines = formatToolOutput(
4088
4261
  entry.name,
@@ -4108,9 +4281,9 @@ var Entry = React5.memo(function Entry2({
4108
4281
  })();
4109
4282
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
4110
4283
  /* @__PURE__ */ jsxs(Text, { children: [
4111
- /* @__PURE__ */ jsx(Text, { color: entry.ok ? theme.success : theme.error, children: entry.ok ? "\u25CF" : "\u2717" }),
4112
- " ",
4113
- /* @__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 }),
4114
4287
  argSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
4115
4288
  /* @__PURE__ */ jsx(Text, { children: " " }),
4116
4289
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
@@ -4377,7 +4550,7 @@ function ScrollableHistory({
4377
4550
  }
4378
4551
  var MB = 1024 * 1024;
4379
4552
  function defaultHeapLogPath() {
4380
- return path4.join(wstackGlobalRoot(), "logs", "heap.jsonl");
4553
+ return path5.join(wstackGlobalRoot(), "logs", "heap.jsonl");
4381
4554
  }
4382
4555
  function takeHeapSample() {
4383
4556
  const m = process.memoryUsage();
@@ -4407,10 +4580,10 @@ function startHeapWatchdog(opts = {}) {
4407
4580
  const append = (line) => {
4408
4581
  writeChain = writeChain.then(async () => {
4409
4582
  if (!dirReady) {
4410
- await fs2.mkdir(path4.dirname(logPath), { recursive: true });
4583
+ await fs3.mkdir(path5.dirname(logPath), { recursive: true });
4411
4584
  dirReady = true;
4412
4585
  }
4413
- await fs2.appendFile(logPath, `${line}
4586
+ await fs3.appendFile(logPath, `${line}
4414
4587
  `, "utf8");
4415
4588
  }).catch(() => void 0);
4416
4589
  };
@@ -4583,6 +4756,52 @@ function layoutInputRows(prompt, value, cursor, width) {
4583
4756
  if (row.length > 0 || rows.length === 0) rows.push(row);
4584
4757
  return rows;
4585
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
+ }
4586
4805
 
4587
4806
  // src/mouse.ts
4588
4807
  var ESC = String.fromCharCode(27);
@@ -4936,12 +5155,8 @@ function PhaseMonitor({
4936
5155
  phases,
4937
5156
  runningPhaseIds,
4938
5157
  elapsedMs,
4939
- nowTick,
4940
- onClose
5158
+ nowTick
4941
5159
  }) {
4942
- useInput((_, key) => {
4943
- if (key.escape) onClose();
4944
- });
4945
5160
  const phaseList = Object.values(phases);
4946
5161
  const running = phaseList.filter(
4947
5162
  (p) => runningPhaseIds.includes(Object.keys(phases).find((k) => phases[k] === p) ?? "")
@@ -4973,7 +5188,7 @@ function PhaseMonitor({
4973
5188
  failed.length
4974
5189
  ] })
4975
5190
  ] }) : null,
4976
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Ctrl+P / Esc to close" })
5191
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Ctrl+P to close" })
4977
5192
  ] }),
4978
5193
  phaseList.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No phases active. Use /autophase start [title] to begin." }) : phaseList.map((phase, i) => {
4979
5194
  const s2 = fmtPhase(phase.status);
@@ -5002,7 +5217,7 @@ function PhaseMonitor({
5002
5217
  ] })
5003
5218
  ] }) }, phaseKey);
5004
5219
  }),
5005
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate phases \xB7 Esc close" }) })
5220
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate phases" }) })
5006
5221
  ] });
5007
5222
  }
5008
5223
  var fmtElapsed3 = (ms) => {
@@ -5183,7 +5398,7 @@ function QueuePanel({ items }) {
5183
5398
  "+",
5184
5399
  overflow
5185
5400
  ] }) : null,
5186
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 F7 / Esc to close" })
5401
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 F7 to close" })
5187
5402
  ] }) }),
5188
5403
  items.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No queued messages" }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
5189
5404
  visible.map((item, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexShrink: 0, children: [
@@ -5259,7 +5474,7 @@ function ProcessListMonitor() {
5259
5474
  breakerState,
5260
5475
  b.state !== "closed" ? ` fail=${b.consecutiveFailures}/5 slow=${b.slowCallsInWindow}/3` : ""
5261
5476
  ] }),
5262
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 F8 / Esc close" })
5477
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 F8 to close" })
5263
5478
  ] }),
5264
5479
  all.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No active processes. Bash/exec spawns appear here." }) : null,
5265
5480
  all.map((p, i) => {
@@ -5288,7 +5503,25 @@ function ProcessListMonitor() {
5288
5503
  ] })
5289
5504
  ] });
5290
5505
  }
5291
- 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
+ });
5292
5525
  if (!goal) {
5293
5526
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
5294
5527
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: "\u{1F3AF} Goal" }) }),
@@ -5300,7 +5533,11 @@ function GoalPanel({ goal }) {
5300
5533
  ] }),
5301
5534
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " to create one." })
5302
5535
  ] }),
5303
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press F9 or Esc 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" }) }) })
5304
5541
  ] });
5305
5542
  }
5306
5543
  const displayGoal = goal.refinedGoal || goal.goal;
@@ -5366,7 +5603,11 @@ function GoalPanel({ goal }) {
5366
5603
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Last task: " }),
5367
5604
  /* @__PURE__ */ jsx(Text, { children: goal.lastTask.length > 50 ? goal.lastTask.slice(0, 47) + "\u2026" : goal.lastTask })
5368
5605
  ] }),
5369
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press F9 or Esc 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" }) }) })
5370
5611
  ] })
5371
5612
  ] });
5372
5613
  }
@@ -5391,6 +5632,261 @@ function renderProgressBar(progress, trend) {
5391
5632
  ] })
5392
5633
  ] });
5393
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
+ }
5781
+ var KIND_COLOR = {
5782
+ goal: "cyan",
5783
+ task: "yellow",
5784
+ knowledge: "green",
5785
+ consensus: "magenta",
5786
+ deadlock: "red"
5787
+ };
5788
+ function fmtElapsed4(at, nowTick) {
5789
+ const s2 = Math.floor((nowTick - at) / 1e3);
5790
+ if (s2 < 60) return `${s2}s ago`;
5791
+ const m = Math.floor(s2 / 60);
5792
+ if (m < 60) return `${m}m ago`;
5793
+ return `${Math.floor(m / 60)}h ago`;
5794
+ }
5795
+ function GoalRow({
5796
+ goal
5797
+ }) {
5798
+ const statusColor = {
5799
+ active: "green",
5800
+ paused: "yellow",
5801
+ completed: "gray",
5802
+ failed: "red"
5803
+ };
5804
+ const color = statusColor[goal.status] ?? "gray";
5805
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
5806
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color, bold: true, children: [
5807
+ goal.status === "active" ? "\u25B6" : goal.status === "paused" ? "\u23F8" : goal.status === "completed" ? "\u2713" : "\u2717",
5808
+ " ",
5809
+ goal.title || "(unnamed goal)"
5810
+ ] }) }),
5811
+ goal.tasks.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingLeft: 2, children: goal.tasks.map((task) => {
5812
+ const taskColor = {
5813
+ pending: "gray",
5814
+ running: "yellow",
5815
+ done: "green",
5816
+ failed: "red"
5817
+ };
5818
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: taskColor[task.status] ?? "gray", children: [
5819
+ task.status === "pending" ? "\u25CB" : task.status === "running" ? "\u25B6" : task.status === "done" ? "\u2713" : "\u2717",
5820
+ " ",
5821
+ task.title,
5822
+ task.assignedTo ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5823
+ " \u2192 ",
5824
+ task.assignedTo
5825
+ ] }) : null
5826
+ ] }) }, task.id);
5827
+ }) }),
5828
+ goal.participants.length > 0 && /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
5829
+ "participants: ",
5830
+ goal.participants.join(", ")
5831
+ ] }) })
5832
+ ] }, goal.id);
5833
+ }
5834
+ function CoordinatorPanel({
5835
+ coordinator,
5836
+ nowTick,
5837
+ onClose
5838
+ }) {
5839
+ const handleInput = useCallback(
5840
+ (input, _key) => {
5841
+ if (input === "q" || input === "Q" || input === "\x1B") {
5842
+ onClose();
5843
+ }
5844
+ },
5845
+ [onClose]
5846
+ );
5847
+ useInput(handleInput);
5848
+ const { goals, timeline, knowledgeCount, healthy } = coordinator;
5849
+ return /* @__PURE__ */ jsxs(
5850
+ Box,
5851
+ {
5852
+ flexDirection: "column",
5853
+ borderStyle: "round",
5854
+ borderColor: "cyan",
5855
+ paddingX: 1,
5856
+ height: Math.min(30, Math.max(10, goals.length * 4 + timeline.length + 8)),
5857
+ width: 80,
5858
+ children: [
5859
+ /* @__PURE__ */ jsxs(Box, { borderStyle: "bold", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
5860
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u{1F916} AutonomousCoordinator" }),
5861
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
5862
+ /* @__PURE__ */ jsx(Text, { dimColor: !healthy, color: healthy ? "green" : "red", children: healthy ? "\u25CF connected" : "\u25CB disconnected" }),
5863
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 q/esc to close" })
5864
+ ] }),
5865
+ goals.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
5866
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
5867
+ "Goals (",
5868
+ goals.length,
5869
+ ")"
5870
+ ] }),
5871
+ goals.map((goal) => /* @__PURE__ */ jsx(GoalRow, { goal }, goal.id))
5872
+ ] }),
5873
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
5874
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Knowledge " }),
5875
+ /* @__PURE__ */ jsx(Text, { color: "green", children: knowledgeCount }),
5876
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " shared facts" })
5877
+ ] }),
5878
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
5879
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Activity" }),
5880
+ timeline.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " No activity yet" }) : timeline.slice(0, 10).map((entry, i) => /* @__PURE__ */ jsxs(Box, { alignItems: "flex-start", children: [
5881
+ /* @__PURE__ */ jsx(Text, { color: KIND_COLOR[entry.kind] ?? "gray", dimColor: i > 2, children: entry.icon }),
5882
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, marginLeft: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: i > 2, children: entry.text }) }),
5883
+ /* @__PURE__ */ jsx(Box, { marginLeft: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: fmtElapsed4(entry.at, nowTick) }) })
5884
+ ] }, i))
5885
+ ] })
5886
+ ]
5887
+ }
5888
+ );
5889
+ }
5394
5890
  function ResumePicker({
5395
5891
  sessions,
5396
5892
  selected,
@@ -5498,7 +5994,7 @@ function SessionsPanel({
5498
5994
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, flexShrink: 0, children: [
5499
5995
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
5500
5996
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u29C9 Sessions" }),
5501
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 F10 or Esc to close" }),
5997
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 F10 to close" }),
5502
5998
  busy && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 loading\u2026" })
5503
5999
  ] }),
5504
6000
  resumeConfirm ? /* @__PURE__ */ jsxs(Box, { marginY: 1, borderStyle: "single", borderColor: "yellow", paddingX: 1, children: [
@@ -5594,6 +6090,14 @@ var MAX_ITERATIONS_PRESETS = [100, 200, 500, 1e3, 0];
5594
6090
  var AUTO_PROCEED_MAX_PRESETS = [10, 25, 50, 100, 250, 0];
5595
6091
  var ENHANCE_DELAY_PRESETS = [3e4, 45e3, 6e4, 9e4, 12e4];
5596
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
+ };
5597
6101
  function formatSettingsDelay(ms) {
5598
6102
  if (ms === 0) return "disabled";
5599
6103
  if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
@@ -5611,7 +6115,7 @@ var MODE_DESC = {
5611
6115
  suggest: "Shows next-step suggestions after each turn",
5612
6116
  auto: "Self-driving \u2014 agent continues automatically"
5613
6117
  };
5614
- var SETTINGS_FIELD_COUNT = 26;
6118
+ var SETTINGS_FIELD_COUNT = 27;
5615
6119
  var CONFIG_SCOPES = ["global", "project"];
5616
6120
  function SettingsPicker({
5617
6121
  field,
@@ -5628,7 +6132,8 @@ function SettingsPicker({
5628
6132
  featureMemory,
5629
6133
  featureSkills,
5630
6134
  featureModelsRegistry,
5631
- featureTokenSaving,
6135
+ tokenSavingTier,
6136
+ allowOutsideProjectRoot,
5632
6137
  contextAutoCompact,
5633
6138
  contextStrategy,
5634
6139
  logLevel,
@@ -5714,8 +6219,13 @@ function SettingsPicker({
5714
6219
  },
5715
6220
  {
5716
6221
  label: "Token-saving mode",
5717
- value: boolVal(featureTokenSaving),
5718
- detail: "Omit non-essential tools and trim system prompt to save tokens"
6222
+ value: tokenSavingTier,
6223
+ detail: TOKEN_SAVING_TIER_DESCS[tokenSavingTier]
6224
+ },
6225
+ {
6226
+ label: "Allow outside project",
6227
+ value: boolVal(allowOutsideProjectRoot),
6228
+ detail: "Allow tools to access paths outside project root"
5719
6229
  },
5720
6230
  // ── Context ──
5721
6231
  { section: "Context" },
@@ -5822,7 +6332,7 @@ function SettingsPicker({
5822
6332
  };
5823
6333
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
5824
6334
  /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Settings \u2501\u2501" }),
5825
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 field \xB7 \u2190/\u2192 change (instant save) \xB7 Esc / F5 close" }),
6335
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 field \xB7 \u2190/\u2192 change (instant apply) \xB7 F5 to close" }),
5826
6336
  hasAbove ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2191 ${windowStart} field${windowStart === 1 ? "" : "s"} above` }) : null,
5827
6337
  rows.map((row, i) => {
5828
6338
  const fieldAtRow = fieldRowIndex.indexOf(i);
@@ -6072,7 +6582,7 @@ function TodosMonitor({ todos }) {
6072
6582
  done
6073
6583
  ] })
6074
6584
  ] }) : null,
6075
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 F6 / Esc to close" })
6585
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 F6 to close" })
6076
6586
  ] }),
6077
6587
  todos.length === 0 ? /* @__PURE__ */ jsx(Box, { marginY: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No todos. The agent will create them as it plans work." }) }) : twoCols ? (
6078
6588
  /* Two-column layout: split the list in half, render side-by-side.
@@ -6087,7 +6597,7 @@ function TodosMonitor({ todos }) {
6087
6597
  )
6088
6598
  ] });
6089
6599
  }
6090
- var fmtElapsed4 = (ms) => {
6600
+ var fmtElapsed5 = (ms) => {
6091
6601
  const s2 = Math.floor(ms / 1e3);
6092
6602
  const m = Math.floor(s2 / 60);
6093
6603
  const h = Math.floor(m / 60);
@@ -6114,7 +6624,7 @@ function WorktreeMonitor({
6114
6624
  onClose
6115
6625
  }) {
6116
6626
  useInput((input, key) => {
6117
- if (key.escape || key.ctrl && input === "w") onClose();
6627
+ if (key.ctrl && input === "w") onClose();
6118
6628
  });
6119
6629
  const TERMINAL_TTL_MS = 5 * 6e4;
6120
6630
  const list = Object.values(worktrees);
@@ -6151,12 +6661,12 @@ function WorktreeMonitor({
6151
6661
  failed
6152
6662
  ] })
6153
6663
  ] }) : null,
6154
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Ctrl+T / F4 / Esc to close" })
6664
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Ctrl+T / F4 to close" })
6155
6665
  ] }),
6156
6666
  recent.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No worktrees. They appear when AutoPhase runs with isolation on." }) : recent.map((w) => {
6157
6667
  const s2 = fmt(w.status);
6158
6668
  const short = w.branch.replace(/^wstack\/ap\//, "");
6159
- const elapsed = w.allocatedAt ? fmtElapsed4(nowTick - w.allocatedAt) : "\u2014";
6669
+ const elapsed = w.allocatedAt ? fmtElapsed5(nowTick - w.allocatedAt) : "\u2014";
6160
6670
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
6161
6671
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
6162
6672
  /* @__PURE__ */ jsx(Text, { color: s2.color, bold: true, children: s2.icon }),
@@ -6201,12 +6711,12 @@ function WorktreeMonitor({
6201
6711
  ] }, w.branch);
6202
6712
  }),
6203
6713
  /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
6204
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Esc close \xB7 merge conflicts with /worktree merge <branch>" }),
6714
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Ctrl+T to close \xB7 merge conflicts with /worktree merge <branch>" }),
6205
6715
  staleTerminal > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${staleTerminal} terminal pruned` }) : null
6206
6716
  ] })
6207
6717
  ] });
6208
6718
  }
6209
- var fmtElapsed5 = (ms) => {
6719
+ var fmtElapsed6 = (ms) => {
6210
6720
  const s2 = Math.floor(ms / 1e3);
6211
6721
  const m = Math.floor(s2 / 60);
6212
6722
  const h = Math.floor(m / 60);
@@ -6272,7 +6782,7 @@ function WorktreePanel({
6272
6782
  list.map((w) => {
6273
6783
  const s2 = st(w.status);
6274
6784
  const conflict = w.status === "needs-review";
6275
- const elapsed = w.allocatedAt ? fmtElapsed5(nowTick - w.allocatedAt) : "";
6785
+ const elapsed = w.allocatedAt ? fmtElapsed6(nowTick - w.allocatedAt) : "";
6276
6786
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
6277
6787
  /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.icon }),
6278
6788
  /* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
@@ -6327,10 +6837,10 @@ async function loadIndex(root) {
6327
6837
  async function walk(root, rel, depth, out) {
6328
6838
  if (out.length >= MAX_FILES_INDEXED) return;
6329
6839
  if (depth > MAX_DEPTH) return;
6330
- const dir = rel ? path4.join(root, rel) : root;
6840
+ const dir = rel ? path5.join(root, rel) : root;
6331
6841
  let entries;
6332
6842
  try {
6333
- entries = await fs2.readdir(dir, { withFileTypes: true });
6843
+ entries = await fs3.readdir(dir, { withFileTypes: true });
6334
6844
  } catch {
6335
6845
  return;
6336
6846
  }
@@ -6817,6 +7327,21 @@ function handleCollabDone(event, dispatch, stateRef) {
6817
7327
  verdict: payload.report.overallVerdict ?? "needs_revision"
6818
7328
  });
6819
7329
  }
7330
+ function useAutonomousCoordinator(subscribeCoordinatorEvents, dispatch) {
7331
+ const dispatchRef = useRef(dispatch);
7332
+ dispatchRef.current = dispatch;
7333
+ const handler = useCallback((event) => {
7334
+ dispatchRef.current({
7335
+ type: "coordinatorEvent",
7336
+ event
7337
+ });
7338
+ }, []);
7339
+ useEffect(() => {
7340
+ if (!subscribeCoordinatorEvents) return;
7341
+ const unsubscribe = subscribeCoordinatorEvents(handler);
7342
+ return unsubscribe;
7343
+ }, [subscribeCoordinatorEvents, handler]);
7344
+ }
6820
7345
  function useStatuslineState(opts) {
6821
7346
  const [liveModel, setLiveModel] = useState(opts.model);
6822
7347
  const [liveProvider, setLiveProvider] = useState(opts.provider ?? "agent");
@@ -7568,11 +8093,17 @@ function reducer(state, action) {
7568
8093
  const banner = state.entries.find((e) => e.kind === "banner");
7569
8094
  return {
7570
8095
  ...state,
7571
- entries: banner ? [banner] : state.entries,
8096
+ entries: banner ? [banner] : [],
7572
8097
  queue: [],
7573
8098
  nextQueueId: 1,
7574
8099
  scrollOffset: 0,
7575
8100
  pendingNewLines: 0,
8101
+ // Bump the generation so <Static> remounts — without this, Ink's
8102
+ // already-written index exceeds the new (shorter) array and the
8103
+ // committed entries stay on screen even though `state.entries` no
8104
+ // longer references them. /clear would otherwise appear to do
8105
+ // nothing to the visible chat history.
8106
+ historyGen: state.historyGen + 1,
7576
8107
  // Reset fleet state on /clear so old subagent entries don't
7577
8108
  // cause the LiveActivityStrip to render stale spacers, and
7578
8109
  // the fleet cost/tokens chips show zero.
@@ -7913,7 +8444,8 @@ function reducer(state, action) {
7913
8444
  featureMemory: action.featureMemory,
7914
8445
  featureSkills: action.featureSkills,
7915
8446
  featureModelsRegistry: action.featureModelsRegistry,
7916
- featureTokenSaving: action.featureTokenSaving,
8447
+ tokenSavingTier: action.tokenSavingTier,
8448
+ allowOutsideProjectRoot: action.allowOutsideProjectRoot,
7917
8449
  contextAutoCompact: action.contextAutoCompact,
7918
8450
  contextStrategy: action.contextStrategy,
7919
8451
  logLevel: action.logLevel,
@@ -7948,6 +8480,7 @@ function reducer(state, action) {
7948
8480
  case "settingsValueChange": {
7949
8481
  const sp = state.settingsPicker;
7950
8482
  const f = sp.field;
8483
+ const bootHint = "\u21BB Takes effect next session";
7951
8484
  if (f === 0) {
7952
8485
  const i = SETTINGS_MODES.indexOf(sp.mode);
7953
8486
  const base = i < 0 ? 0 : i;
@@ -7966,59 +8499,65 @@ function reducer(state, action) {
7966
8499
  if (f === 5) return { ...state, settingsPicker: { ...sp, chime: !sp.chime, hint: void 0 } };
7967
8500
  if (f === 6) return { ...state, settingsPicker: { ...sp, confirmExit: !sp.confirmExit, hint: void 0 } };
7968
8501
  if (f === 7) return { ...state, settingsPicker: { ...sp, nextPrediction: !sp.nextPrediction, hint: void 0 } };
7969
- if (f === 8) return { ...state, settingsPicker: { ...sp, featureMcp: !sp.featureMcp, hint: void 0 } };
7970
- if (f === 9) return { ...state, settingsPicker: { ...sp, featurePlugins: !sp.featurePlugins, hint: void 0 } };
7971
- if (f === 10) return { ...state, settingsPicker: { ...sp, featureMemory: !sp.featureMemory, hint: void 0 } };
7972
- if (f === 11) return { ...state, settingsPicker: { ...sp, featureSkills: !sp.featureSkills, hint: void 0 } };
7973
- if (f === 12) return { ...state, settingsPicker: { ...sp, featureModelsRegistry: !sp.featureModelsRegistry, hint: void 0 } };
7974
- if (f === 13) return { ...state, settingsPicker: { ...sp, featureTokenSaving: !sp.featureTokenSaving, hint: void 0 } };
7975
- if (f === 14) return { ...state, settingsPicker: { ...sp, contextAutoCompact: !sp.contextAutoCompact, hint: void 0 } };
7976
- if (f === 15) {
8502
+ if (f === 8) return { ...state, settingsPicker: { ...sp, featureMcp: !sp.featureMcp, hint: bootHint } };
8503
+ if (f === 9) return { ...state, settingsPicker: { ...sp, featurePlugins: !sp.featurePlugins, hint: bootHint } };
8504
+ if (f === 10) return { ...state, settingsPicker: { ...sp, featureMemory: !sp.featureMemory, hint: bootHint } };
8505
+ if (f === 11) return { ...state, settingsPicker: { ...sp, featureSkills: !sp.featureSkills, hint: bootHint } };
8506
+ if (f === 12) return { ...state, settingsPicker: { ...sp, featureModelsRegistry: !sp.featureModelsRegistry, 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
+ }
8513
+ if (f === 14) return { ...state, settingsPicker: { ...sp, allowOutsideProjectRoot: !sp.allowOutsideProjectRoot, hint: void 0 } };
8514
+ if (f === 15) return { ...state, settingsPicker: { ...sp, contextAutoCompact: !sp.contextAutoCompact, hint: void 0 } };
8515
+ if (f === 16) {
7977
8516
  const i = COMPACTOR_STRATEGIES.indexOf(sp.contextStrategy);
7978
8517
  const base = i < 0 ? 0 : i;
7979
8518
  const next = (base + action.delta + COMPACTOR_STRATEGIES.length) % COMPACTOR_STRATEGIES.length;
7980
- return { ...state, settingsPicker: { ...sp, contextStrategy: expectDefined$1(COMPACTOR_STRATEGIES[next]), hint: void 0 } };
8519
+ return { ...state, settingsPicker: { ...sp, contextStrategy: expectDefined$1(COMPACTOR_STRATEGIES[next]), hint: bootHint } };
7981
8520
  }
7982
- if (f === 16) {
8521
+ if (f === 17) {
7983
8522
  const i = LOG_LEVELS.indexOf(sp.logLevel);
7984
8523
  const base = i < 0 ? 0 : i;
7985
8524
  const next = (base + action.delta + LOG_LEVELS.length) % LOG_LEVELS.length;
7986
8525
  return { ...state, settingsPicker: { ...sp, logLevel: expectDefined$1(LOG_LEVELS[next]), hint: void 0 } };
7987
8526
  }
7988
- if (f === 17) {
8527
+ if (f === 18) {
7989
8528
  const i = AUDIT_LEVELS.indexOf(sp.auditLevel);
7990
8529
  const base = i < 0 ? 0 : i;
7991
8530
  const next = (base + action.delta + AUDIT_LEVELS.length) % AUDIT_LEVELS.length;
7992
8531
  return { ...state, settingsPicker: { ...sp, auditLevel: expectDefined$1(AUDIT_LEVELS[next]), hint: void 0 } };
7993
8532
  }
7994
- if (f === 18) return { ...state, settingsPicker: { ...sp, indexOnStart: !sp.indexOnStart, hint: void 0 } };
7995
- if (f === 19) {
8533
+ if (f === 19) return { ...state, settingsPicker: { ...sp, indexOnStart: !sp.indexOnStart, hint: bootHint } };
8534
+ if (f === 20) {
7996
8535
  const j = MAX_ITERATIONS_PRESETS.indexOf(sp.maxIterations);
7997
8536
  const base = j < 0 ? 0 : j;
7998
8537
  const next = (base + action.delta + MAX_ITERATIONS_PRESETS.length) % MAX_ITERATIONS_PRESETS.length;
7999
8538
  return { ...state, settingsPicker: { ...sp, maxIterations: expectDefined$1(MAX_ITERATIONS_PRESETS[next]), hint: void 0 } };
8000
8539
  }
8001
- if (f === 20) {
8540
+ if (f === 21) {
8002
8541
  const aj = AUTO_PROCEED_MAX_PRESETS.indexOf(sp.autoProceedMaxIterations);
8003
8542
  const abase = aj < 0 ? 0 : aj;
8004
8543
  const anext = (abase + action.delta + AUTO_PROCEED_MAX_PRESETS.length) % AUTO_PROCEED_MAX_PRESETS.length;
8005
8544
  return { ...state, settingsPicker: { ...sp, autoProceedMaxIterations: expectDefined$1(AUTO_PROCEED_MAX_PRESETS[anext]), hint: void 0 } };
8006
8545
  }
8007
- if (f === 21) {
8546
+ if (f === 22) {
8008
8547
  const ej = ENHANCE_DELAY_PRESETS.indexOf(sp.enhanceDelayMs);
8009
8548
  const ebase = ej < 0 ? 0 : ej;
8010
8549
  const enext = (ebase + action.delta + ENHANCE_DELAY_PRESETS.length) % ENHANCE_DELAY_PRESETS.length;
8011
8550
  return { ...state, settingsPicker: { ...sp, enhanceDelayMs: expectDefined$1(ENHANCE_DELAY_PRESETS[enext]), hint: void 0 } };
8012
8551
  }
8013
- if (f === 22) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
8014
- if (f === 23) {
8552
+ if (f === 23) return { ...state, settingsPicker: { ...sp, enhanceEnabled: !sp.enhanceEnabled, hint: void 0 } };
8553
+ if (f === 24) {
8015
8554
  const i = ENHANCE_LANGUAGES.indexOf(sp.enhanceLanguage);
8016
8555
  const base = i < 0 ? 0 : i;
8017
8556
  const next = (base + action.delta + ENHANCE_LANGUAGES.length) % ENHANCE_LANGUAGES.length;
8018
8557
  return { ...state, settingsPicker: { ...sp, enhanceLanguage: expectDefined$1(ENHANCE_LANGUAGES[next]), hint: void 0 } };
8019
8558
  }
8020
- if (f === 24) return { ...state, settingsPicker: { ...sp, debugStream: !sp.debugStream, hint: void 0 } };
8021
- if (f === 25) {
8559
+ if (f === 25) return { ...state, settingsPicker: { ...sp, debugStream: !sp.debugStream, hint: void 0 } };
8560
+ if (f === 26) {
8022
8561
  const i = CONFIG_SCOPES.indexOf(sp.configScope);
8023
8562
  const base = i < 0 ? 0 : i;
8024
8563
  const next = (base + action.delta + CONFIG_SCOPES.length) % CONFIG_SCOPES.length;
@@ -8429,6 +8968,10 @@ function reducer(state, action) {
8429
8968
  const opening = !state.processListOpen;
8430
8969
  return opening ? { ...state, processListOpen: true, monitorOpen: false, agentsMonitorOpen: false, helpOpen: false, todosMonitorOpen: false, queuePanelOpen: false, goalPanelOpen: false } : { ...state, processListOpen: false };
8431
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
+ }
8432
8975
  case "toggleGoalPanel": {
8433
8976
  const opening = !state.goalPanelOpen;
8434
8977
  return opening ? { ...state, goalPanelOpen: true, monitorOpen: false, agentsMonitorOpen: false, helpOpen: false, todosMonitorOpen: false, queuePanelOpen: false, processListOpen: false } : { ...state, goalPanelOpen: false };
@@ -8749,9 +9292,10 @@ function reducer(state, action) {
8749
9292
  return { ...state, sessionsPanelOpen: !state.sessionsPanelOpen };
8750
9293
  }
8751
9294
  case "sessionsPanelSet": {
9295
+ const sessions = Array.isArray(action.sessions) ? action.sessions : [];
8752
9296
  return {
8753
9297
  ...state,
8754
- sessionsPanel: { sessions: action.sessions, busy: false, selected: action.sessions.length > 0 ? 0 : -1 }
9298
+ sessionsPanel: { sessions, busy: false, selected: sessions.length > 0 ? 0 : -1 }
8755
9299
  };
8756
9300
  }
8757
9301
  case "sessionsPanelMove": {
@@ -8786,6 +9330,81 @@ function reducer(state, action) {
8786
9330
  if (state.countdown === null) return state;
8787
9331
  return { ...state, countdown: null };
8788
9332
  }
9333
+ // --- AutonomousCoordinator ---
9334
+ case "coordinatorEvent": {
9335
+ const { event } = action;
9336
+ const now = Date.now();
9337
+ let kind;
9338
+ let icon;
9339
+ switch (event.type) {
9340
+ case "goal:added":
9341
+ kind = "goal";
9342
+ icon = "\u{1F3AF}";
9343
+ break;
9344
+ case "goal:completed":
9345
+ kind = "goal";
9346
+ icon = "\u2705";
9347
+ break;
9348
+ case "goal:failed":
9349
+ kind = "goal";
9350
+ icon = "\u274C";
9351
+ break;
9352
+ case "task:ready":
9353
+ kind = "task";
9354
+ icon = "\u26A1";
9355
+ break;
9356
+ case "task:completed":
9357
+ kind = "task";
9358
+ icon = "\u2713";
9359
+ break;
9360
+ case "knowledge:added":
9361
+ kind = "knowledge";
9362
+ icon = "\u{1F4A1}";
9363
+ break;
9364
+ case "consensus:reached":
9365
+ kind = "consensus";
9366
+ icon = "\u{1F91D}";
9367
+ break;
9368
+ case "deadlock:detected":
9369
+ kind = "deadlock";
9370
+ icon = "\u26A0\uFE0F";
9371
+ break;
9372
+ default:
9373
+ kind = "goal";
9374
+ icon = "\u2022";
9375
+ break;
9376
+ }
9377
+ const timelineEntry = {
9378
+ at: now,
9379
+ kind,
9380
+ icon,
9381
+ text: event.text ?? event.type
9382
+ };
9383
+ return {
9384
+ ...state,
9385
+ coordinator: {
9386
+ ...state.coordinator,
9387
+ healthy: true,
9388
+ knowledgeCount: event.type === "knowledge:added" ? state.coordinator.knowledgeCount + 1 : state.coordinator.knowledgeCount,
9389
+ timeline: [timelineEntry, ...state.coordinator.timeline].slice(0, 50)
9390
+ }
9391
+ };
9392
+ }
9393
+ case "toggleCoordinatorMonitor": {
9394
+ const opening = !state.coordinator.monitorOpen;
9395
+ return opening ? {
9396
+ ...state,
9397
+ coordinator: { ...state.coordinator, monitorOpen: true },
9398
+ // Close other monitors when opening coordinator
9399
+ monitorOpen: false,
9400
+ agentsMonitorOpen: false,
9401
+ helpOpen: false,
9402
+ todosMonitorOpen: false,
9403
+ queuePanelOpen: false,
9404
+ processListOpen: false,
9405
+ goalPanelOpen: false
9406
+ } : { ...state, coordinator: { ...state.coordinator, monitorOpen: false } };
9407
+ }
8789
9408
  }
8790
9409
  }
8791
9410
  var INPUT_PROMPT = "\u203A ";
@@ -8840,6 +9459,7 @@ function App({
8840
9459
  yolo = false,
8841
9460
  chime = false,
8842
9461
  confirmExit = true,
9462
+ titleController,
8843
9463
  mouse = false,
8844
9464
  enhanceEnabled = true,
8845
9465
  enhanceController,
@@ -8895,7 +9515,12 @@ function App({
8895
9515
  requestExit,
8896
9516
  getLiveSessions,
8897
9517
  onSwitchToSession,
8898
- initialAgentsMonitorOpen
9518
+ initialAgentsMonitorOpen,
9519
+ subscribeCoordinatorEvents,
9520
+ onCoordinatorStart,
9521
+ onCoordinatorStop,
9522
+ coordinatorRunning = false,
9523
+ clientId
8899
9524
  }) {
8900
9525
  const { exit } = useApp();
8901
9526
  const { stdout } = useStdout();
@@ -8931,6 +9556,28 @@ function App({
8931
9556
  setIndexState(getIndexState());
8932
9557
  return onIndexStateChange((next) => setIndexState(next));
8933
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]);
8934
9581
  useEffect(() => {
8935
9582
  setHiddenItems([...statuslineHiddenItems]);
8936
9583
  }, [statuslineHiddenItems]);
@@ -9028,7 +9675,7 @@ function App({
9028
9675
  },
9029
9676
  autonomyPicker: { open: false, options: [], selected: 0 },
9030
9677
  resumePicker: { open: false, sessions: [], selected: 0, busy: false, hint: void 0, error: void 0 },
9031
- 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" },
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" },
9032
9679
  projectPicker: { open: false, allItems: [], items: [], selected: 0, filter: "", hint: void 0 },
9033
9680
  confirmQueue: [],
9034
9681
  enhance: null,
@@ -9056,6 +9703,7 @@ function App({
9056
9703
  todosMonitorOpen: false,
9057
9704
  queuePanelOpen: false,
9058
9705
  processListOpen: false,
9706
+ planPanelOpen: false,
9059
9707
  goalPanelOpen: false,
9060
9708
  sessionsPanelOpen: false,
9061
9709
  sessionsPanel: { sessions: [], busy: false, selected: -1 },
@@ -9068,6 +9716,13 @@ function App({
9068
9716
  autoPhase: null,
9069
9717
  worktrees: {},
9070
9718
  worktreeMonitorOpen: false,
9719
+ coordinator: {
9720
+ goals: [],
9721
+ timeline: [],
9722
+ knowledgeCount: 0,
9723
+ monitorOpen: false,
9724
+ healthy: false
9725
+ },
9071
9726
  scrollOffset: 0,
9072
9727
  totalLines: 0,
9073
9728
  viewportRows: 0,
@@ -9075,6 +9730,7 @@ function App({
9075
9730
  debugStreamStats: null,
9076
9731
  countdown: null
9077
9732
  });
9733
+ useAutonomousCoordinator(subscribeCoordinatorEvents, dispatch);
9078
9734
  const builderRef = useRef(null);
9079
9735
  if (builderRef.current === null) {
9080
9736
  builderRef.current = new InputBuilder({ store: attachments });
@@ -9087,27 +9743,33 @@ function App({
9087
9743
  const lastEnterAtRef = useRef(0);
9088
9744
  const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
9089
9745
  const projectName = React5.useMemo(() => {
9090
- const base = path4.basename(projectRoot);
9091
- return base && base !== path4.sep ? base : void 0;
9746
+ const base = path5.basename(projectRoot);
9747
+ return base && base !== path5.sep ? base : void 0;
9092
9748
  }, [projectRoot]);
9093
9749
  const [workingDirChip, setWorkingDirChip] = React5.useState(() => {
9094
9750
  const ctx = agent.ctx;
9095
9751
  if (ctx.workingDir && ctx.workingDir !== projectRoot) {
9096
- return path4.relative(projectRoot, ctx.workingDir) || ".";
9752
+ return path5.relative(projectRoot, ctx.workingDir) || ".";
9097
9753
  }
9098
9754
  return void 0;
9099
9755
  });
9100
9756
  React5.useEffect(() => {
9101
9757
  const ctx = agent.ctx;
9102
9758
  return ctx.onWorkingDirChanged((newDir) => {
9103
- const rel = path4.relative(projectRoot, newDir) || ".";
9759
+ const rel = path5.relative(projectRoot, newDir) || ".";
9104
9760
  setWorkingDirChip(rel === "." ? void 0 : rel);
9105
9761
  });
9106
9762
  }, [agent.ctx, projectRoot]);
9763
+ const liveSettings = getSettings?.();
9107
9764
  const chimeRef = useRef(chime);
9108
- chimeRef.current = chime;
9765
+ chimeRef.current = liveSettings?.chime ?? chime;
9109
9766
  const confirmExitRef = useRef(confirmExit);
9110
- confirmExitRef.current = confirmExit;
9767
+ confirmExitRef.current = liveSettings?.confirmExit ?? confirmExit;
9768
+ const liveTitleAnimation = liveSettings?.titleAnimation;
9769
+ useEffect(() => {
9770
+ if (!titleController) return;
9771
+ titleController.setEnabled(liveTitleAnimation !== false);
9772
+ }, [titleController, liveTitleAnimation]);
9111
9773
  const streamingTextRef = useRef("");
9112
9774
  const pendingDeltaRef = useRef("");
9113
9775
  const flushTimerRef = useRef(null);
@@ -9491,7 +10153,7 @@ function App({
9491
10153
  let cancelled = false;
9492
10154
  const poll = async () => {
9493
10155
  try {
9494
- const data = await fs2.readFile(planPath, "utf8");
10156
+ const data = await fs3.readFile(planPath, "utf8");
9495
10157
  const parsed = JSON.parse(data);
9496
10158
  if (cancelled) return;
9497
10159
  if (!Array.isArray(parsed.items)) {
@@ -9525,7 +10187,7 @@ function App({
9525
10187
  let cancelled = false;
9526
10188
  const poll = async () => {
9527
10189
  try {
9528
- const data = await fs2.readFile(taskPath, "utf8");
10190
+ const data = await fs3.readFile(taskPath, "utf8");
9529
10191
  const parsed = JSON.parse(data);
9530
10192
  if (cancelled) return;
9531
10193
  if (!Array.isArray(parsed.tasks)) {
@@ -9575,7 +10237,7 @@ function App({
9575
10237
  }
9576
10238
  }, []);
9577
10239
  React5.useLayoutEffect(() => {
9578
- const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.resumePicker.open || state.settingsPicker.open || state.enhanceBusy || state.enhance != null || state.escConfirm != null || state.confirmQueue.length > 0;
10240
+ const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.resumePicker.open || state.settingsPicker.open || state.enhanceBusy || state.enhance != null || state.coordinator.monitorOpen || state.escConfirm != null || state.confirmQueue.length > 0;
9579
10241
  const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
9580
10242
  const newEntryCommitted = state.entries.length > prevEntriesCount.current;
9581
10243
  const curToolStreamLen = state.toolStream?.text.length ?? 0;
@@ -9594,6 +10256,7 @@ function App({
9594
10256
  state.settingsPicker.open,
9595
10257
  state.enhanceBusy,
9596
10258
  state.enhance,
10259
+ state.coordinator.monitorOpen,
9597
10260
  state.escConfirm,
9598
10261
  state.confirmQueue.length,
9599
10262
  state.entries.length,
@@ -9618,7 +10281,8 @@ function App({
9618
10281
  queue: stateRef.current.queuePanelOpen,
9619
10282
  processList: stateRef.current.processListOpen,
9620
10283
  goalPanel: stateRef.current.goalPanelOpen,
9621
- sessionsPanel: stateRef.current.sessionsPanelOpen
10284
+ sessionsPanel: stateRef.current.sessionsPanelOpen,
10285
+ coordinator: stateRef.current.coordinator.monitorOpen
9622
10286
  };
9623
10287
  if (stateRef.current.settingsPicker.open) dispatch({ type: "settingsClose" });
9624
10288
  if (stateRef.current.projectPicker.open) dispatch({ type: "projectPickerClose" });
@@ -9635,6 +10299,7 @@ function App({
9635
10299
  if (stateRef.current.todosMonitorOpen) dispatch({ type: "toggleTodosMonitor" });
9636
10300
  if (stateRef.current.queuePanelOpen) dispatch({ type: "toggleQueuePanel" });
9637
10301
  if (stateRef.current.processListOpen) dispatch({ type: "toggleProcessList" });
10302
+ if (stateRef.current.planPanelOpen) dispatch({ type: "togglePlanPanel" });
9638
10303
  if (stateRef.current.goalPanelOpen) dispatch({ type: "toggleGoalPanel" });
9639
10304
  if (stateRef.current.sessionsPanelOpen) dispatch({ type: "toggleSessionsPanel" });
9640
10305
  eraseLiveRegion();
@@ -9660,7 +10325,8 @@ function App({
9660
10325
  featureMemory: sp.featureMemory,
9661
10326
  featureSkills: sp.featureSkills,
9662
10327
  featureModelsRegistry: sp.featureModelsRegistry,
9663
- featureTokenSaving: sp.featureTokenSaving,
10328
+ tokenSavingTier: sp.tokenSavingTier,
10329
+ allowOutsideProjectRoot: sp.allowOutsideProjectRoot,
9664
10330
  contextAutoCompact: sp.contextAutoCompact,
9665
10331
  contextStrategy: sp.contextStrategy,
9666
10332
  logLevel: sp.logLevel,
@@ -9688,6 +10354,7 @@ function App({
9688
10354
  if (prev.processList) dispatch({ type: "toggleProcessList" });
9689
10355
  if (prev.goalPanel) dispatch({ type: "toggleGoalPanel" });
9690
10356
  if (prev.sessionsPanel) dispatch({ type: "toggleSessionsPanel" });
10357
+ if (prev.coordinator) dispatch({ type: "toggleCoordinatorMonitor" });
9691
10358
  preResizePanelsRef.current = null;
9692
10359
  resizeRestoreTimerRef.current = null;
9693
10360
  }, 300);
@@ -9781,7 +10448,28 @@ function App({
9781
10448
  type: "addEntry",
9782
10449
  entry: {
9783
10450
  kind: "error",
9784
- text: `Clipboard image error: ${err instanceof Error ? err.message : String(err)}`
10451
+ text: `Clipboard image error: ${toErrorMessage(err)}`
10452
+ }
10453
+ });
10454
+ }
10455
+ };
10456
+ const pasteClipboardText = async () => {
10457
+ try {
10458
+ const text = await readClipboardText();
10459
+ if (!text) {
10460
+ dispatch({
10461
+ type: "addEntry",
10462
+ entry: { kind: "info", text: "No text on the clipboard." }
10463
+ });
10464
+ return;
10465
+ }
10466
+ await commitPaste(text);
10467
+ } catch (err) {
10468
+ dispatch({
10469
+ type: "addEntry",
10470
+ entry: {
10471
+ kind: "error",
10472
+ text: `Clipboard error: ${toErrorMessage(err)}`
9785
10473
  }
9786
10474
  });
9787
10475
  }
@@ -9799,9 +10487,9 @@ function App({
9799
10487
  dispatch({ type: "pickerClose" });
9800
10488
  return;
9801
10489
  }
9802
- const absPath = path4.isAbsolute(picked) ? picked : path4.join(projectRoot, picked);
10490
+ const absPath = path5.isAbsolute(picked) ? picked : path5.join(projectRoot, picked);
9803
10491
  try {
9804
- const data = await fs2.readFile(absPath, "utf8");
10492
+ const data = await fs3.readFile(absPath, "utf8");
9805
10493
  const token = await builder.registerFile({
9806
10494
  kind: "file",
9807
10495
  data,
@@ -9818,7 +10506,7 @@ function App({
9818
10506
  type: "addEntry",
9819
10507
  entry: {
9820
10508
  kind: "error",
9821
- text: `Attach failed: ${err instanceof Error ? err.message : String(err)}`
10509
+ text: `Attach failed: ${toErrorMessage(err)}`
9822
10510
  }
9823
10511
  });
9824
10512
  dispatch({ type: "pickerClose" });
@@ -10004,7 +10692,10 @@ function App({
10004
10692
  dispatch({ type: "projectPickerOpen", items });
10005
10693
  }, [getProjectPickerItems]);
10006
10694
  const loadLiveSessions = React5.useCallback(async () => {
10007
- if (!getLiveSessions) return;
10695
+ if (!getLiveSessions) {
10696
+ dispatch({ type: "sessionsPanelSet", sessions: [] });
10697
+ return;
10698
+ }
10008
10699
  dispatch({ type: "sessionsPanelBusy", on: true });
10009
10700
  try {
10010
10701
  const sessions = await getLiveSessions();
@@ -10038,7 +10729,8 @@ function App({
10038
10729
  featureMemory: s2.featureMemory ?? true,
10039
10730
  featureSkills: s2.featureSkills ?? true,
10040
10731
  featureModelsRegistry: s2.featureModelsRegistry ?? true,
10041
- featureTokenSaving: s2.featureTokenSaving ?? false,
10732
+ tokenSavingTier: s2.featureTokenSaving ?? "off",
10733
+ allowOutsideProjectRoot: s2.allowOutsideProjectRoot ?? true,
10042
10734
  contextAutoCompact: s2.contextAutoCompact ?? true,
10043
10735
  contextStrategy: s2.contextStrategy ?? "hybrid",
10044
10736
  logLevel: s2.logLevel ?? "info",
@@ -10173,7 +10865,8 @@ function App({
10173
10865
  featureMemory: sp.featureMemory,
10174
10866
  featureSkills: sp.featureSkills,
10175
10867
  featureModelsRegistry: sp.featureModelsRegistry,
10176
- featureTokenSaving: sp.featureTokenSaving,
10868
+ featureTokenSaving: sp.tokenSavingTier,
10869
+ allowOutsideProjectRoot: sp.allowOutsideProjectRoot,
10177
10870
  contextAutoCompact: sp.contextAutoCompact,
10178
10871
  contextStrategy: sp.contextStrategy,
10179
10872
  logLevel: sp.logLevel,
@@ -10204,6 +10897,8 @@ function App({
10204
10897
  state.settingsPicker.featureMemory,
10205
10898
  state.settingsPicker.featureSkills,
10206
10899
  state.settingsPicker.featureModelsRegistry,
10900
+ state.settingsPicker.tokenSavingTier,
10901
+ state.settingsPicker.allowOutsideProjectRoot,
10207
10902
  state.settingsPicker.contextAutoCompact,
10208
10903
  state.settingsPicker.contextStrategy,
10209
10904
  state.settingsPicker.logLevel,
@@ -10212,6 +10907,9 @@ function App({
10212
10907
  state.settingsPicker.maxIterations,
10213
10908
  state.settingsPicker.autoProceedMaxIterations,
10214
10909
  state.settingsPicker.enhanceDelayMs,
10910
+ state.settingsPicker.enhanceEnabled,
10911
+ state.settingsPicker.enhanceLanguage,
10912
+ state.settingsPicker.debugStream,
10215
10913
  saveSettings
10216
10914
  ]);
10217
10915
  useEffect(() => {
@@ -10294,7 +10992,7 @@ function App({
10294
10992
  dispatch({ type: "resumePickerOpen", sessions });
10295
10993
  } catch (err) {
10296
10994
  return {
10297
- message: err instanceof Error ? err.message : String(err)
10995
+ message: toErrorMessage(err)
10298
10996
  };
10299
10997
  }
10300
10998
  return { message: void 0 };
@@ -10474,6 +11172,47 @@ function App({
10474
11172
  if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
10475
11173
  };
10476
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]);
10477
11216
  useEffect(() => {
10478
11217
  if (!registerDebugStreamCallback) return;
10479
11218
  let cancelled = false;
@@ -10897,7 +11636,7 @@ function App({
10897
11636
  }).catch((err) => {
10898
11637
  dispatch({
10899
11638
  type: "resumePickerError",
10900
- text: err instanceof Error ? err.message : String(err)
11639
+ text: toErrorMessage(err)
10901
11640
  });
10902
11641
  });
10903
11642
  return;
@@ -11294,6 +12033,21 @@ function App({
11294
12033
  toggleWorktreeOverlay();
11295
12034
  return;
11296
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
+ }
11297
12051
  if (key.fn === 6) {
11298
12052
  toggleTodosOverlay();
11299
12053
  return;
@@ -11346,7 +12100,7 @@ function App({
11346
12100
  }
11347
12101
  return;
11348
12102
  }
11349
- if (key.fn === 10) {
12103
+ if (key.fn === 10 || key.escape && state.sessionsPanelOpen) {
11350
12104
  if (state.sessionsPanelOpen) {
11351
12105
  dispatch({ type: "toggleSessionsPanel" });
11352
12106
  } else {
@@ -11365,6 +12119,10 @@ function App({
11365
12119
  }
11366
12120
  return;
11367
12121
  }
12122
+ if (key.fn === 11 || input === "\x1B" && state.coordinator.monitorOpen) {
12123
+ dispatch({ type: "toggleCoordinatorMonitor" });
12124
+ return;
12125
+ }
11368
12126
  if (key.ctrl && input === "s") {
11369
12127
  if (state.settingsPicker.open) {
11370
12128
  dispatch({ type: "settingsClose" });
@@ -11392,7 +12150,8 @@ function App({
11392
12150
  featureMemory: cfg.featureMemory ?? true,
11393
12151
  featureSkills: cfg.featureSkills ?? true,
11394
12152
  featureModelsRegistry: cfg.featureModelsRegistry ?? true,
11395
- featureTokenSaving: cfg.featureTokenSaving ?? false,
12153
+ tokenSavingTier: cfg.featureTokenSaving ?? "off",
12154
+ allowOutsideProjectRoot: cfg.allowOutsideProjectRoot ?? true,
11396
12155
  contextAutoCompact: cfg.contextAutoCompact ?? true,
11397
12156
  contextStrategy: cfg.contextStrategy ?? "hybrid",
11398
12157
  logLevel: cfg.logLevel ?? "info",
@@ -11442,10 +12201,18 @@ function App({
11442
12201
  dispatch({ type: "toggleGoalPanel" });
11443
12202
  return;
11444
12203
  }
12204
+ if (state.helpOpen) {
12205
+ dispatch({ type: "toggleHelp" });
12206
+ return;
12207
+ }
11445
12208
  if (state.sessionsPanelOpen) {
11446
12209
  dispatch({ type: "toggleSessionsPanel" });
11447
12210
  return;
11448
12211
  }
12212
+ if (state.coordinator.monitorOpen) {
12213
+ dispatch({ type: "toggleCoordinatorMonitor" });
12214
+ return;
12215
+ }
11449
12216
  }
11450
12217
  if (state.processListOpen) {
11451
12218
  return;
@@ -11468,6 +12235,17 @@ function App({
11468
12235
  return;
11469
12236
  }
11470
12237
  const { buffer, cursor } = draftRef.current;
12238
+ if (key.tab && nextStepsAutoSubmitTimerRef.current != null) {
12239
+ const pending = nextStepsAutoSubmitSuggestionRef.current ?? nextStepsAutoSubmitLabel ?? "";
12240
+ clearInterval(nextStepsAutoSubmitTimerRef.current);
12241
+ nextStepsAutoSubmitTimerRef.current = void 0;
12242
+ setNextStepsAutoSubmitCountdown(null);
12243
+ setNextStepsAutoSubmitLabel(null);
12244
+ nextStepsAutoSubmitSuggestionRef.current = null;
12245
+ const text = pending.trim();
12246
+ if (text) setDraft(text, text.length);
12247
+ return;
12248
+ }
11471
12249
  if (key.backspace) {
11472
12250
  if (key.ctrl) {
11473
12251
  if (cursor === 0) return;
@@ -11555,7 +12333,58 @@ function App({
11555
12333
  setDraft(buffer, buffer.length);
11556
12334
  return;
11557
12335
  }
11558
- const overlayOpen = state.monitorOpen || state.agentsMonitorOpen || state.worktreeMonitorOpen || state.todosMonitorOpen || state.queuePanelOpen || state.processListOpen || state.goalPanelOpen || state.sessionsPanelOpen || state.helpOpen || (state.autoPhase?.monitorOpen ?? false) || state.rewindOverlay !== null;
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
+ }
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;
11559
12388
  if (mouseMode && !overlayOpen) {
11560
12389
  if (key.mouse?.kind === "wheel") {
11561
12390
  if (key.mouse.shift) dispatch({ type: "scrollPage", dir: key.mouse.wheel > 0 ? "up" : "down" });
@@ -11684,6 +12513,10 @@ function App({
11684
12513
  setDraft(next2, cursor);
11685
12514
  return;
11686
12515
  }
12516
+ if (key.ctrl) {
12517
+ await pasteClipboardText();
12518
+ return;
12519
+ }
11687
12520
  if (key.meta && input === "v") {
11688
12521
  await pasteClipboardImage();
11689
12522
  return;
@@ -11807,7 +12640,7 @@ function App({
11807
12640
  } catch (err) {
11808
12641
  dispatch({
11809
12642
  type: "addEntry",
11810
- entry: { kind: "error", text: err instanceof Error ? err.message : String(err) }
12643
+ entry: { kind: "error", text: toErrorMessage(err) }
11811
12644
  });
11812
12645
  } finally {
11813
12646
  activeCtrlRef.current = null;
@@ -11849,7 +12682,7 @@ function App({
11849
12682
  type: "addEntry",
11850
12683
  entry: {
11851
12684
  kind: "error",
11852
- text: `[eternal] ${err instanceof Error ? err.message : String(err)}`
12685
+ text: `[eternal] ${toErrorMessage(err)}`
11853
12686
  }
11854
12687
  });
11855
12688
  }
@@ -11885,7 +12718,7 @@ function App({
11885
12718
  type: "addEntry",
11886
12719
  entry: {
11887
12720
  kind: "error",
11888
- text: `[parallel] ${err instanceof Error ? err.message : String(err)}`
12721
+ text: `[parallel] ${toErrorMessage(err)}`
11889
12722
  }
11890
12723
  });
11891
12724
  }
@@ -12047,7 +12880,7 @@ ${content}
12047
12880
  } catch (err) {
12048
12881
  dispatch({
12049
12882
  type: "addEntry",
12050
- entry: { kind: "error", text: err instanceof Error ? err.message : String(err) }
12883
+ entry: { kind: "error", text: toErrorMessage(err) }
12051
12884
  });
12052
12885
  }
12053
12886
  return;
@@ -12326,7 +13159,8 @@ User message:
12326
13159
  featureMemory: state.settingsPicker.featureMemory,
12327
13160
  featureSkills: state.settingsPicker.featureSkills,
12328
13161
  featureModelsRegistry: state.settingsPicker.featureModelsRegistry,
12329
- featureTokenSaving: state.settingsPicker.featureTokenSaving,
13162
+ tokenSavingTier: state.settingsPicker.tokenSavingTier,
13163
+ allowOutsideProjectRoot: state.settingsPicker.allowOutsideProjectRoot,
12330
13164
  contextAutoCompact: state.settingsPicker.contextAutoCompact,
12331
13165
  contextStrategy: state.settingsPicker.contextStrategy,
12332
13166
  logLevel: state.settingsPicker.logLevel,
@@ -12361,6 +13195,14 @@ User message:
12361
13195
  currentSessionId: agent.ctx.session?.id
12362
13196
  }
12363
13197
  ) : null,
13198
+ state.coordinator.monitorOpen ? /* @__PURE__ */ jsx(
13199
+ CoordinatorPanel,
13200
+ {
13201
+ coordinator: state.coordinator,
13202
+ nowTick,
13203
+ onClose: () => dispatch({ type: "toggleCoordinatorMonitor" })
13204
+ }
13205
+ ) : null,
12364
13206
  state.rewindOverlay ? (() => {
12365
13207
  const overlay = state.rewindOverlay;
12366
13208
  return /* @__PURE__ */ jsx(
@@ -12491,6 +13333,7 @@ User message:
12491
13333
  fleet: fleetCounts,
12492
13334
  git: gitInfo,
12493
13335
  context: contextWindow,
13336
+ contextStrategy: getSettings ? getSettings().contextStrategy : void 0,
12494
13337
  brain: state.brain,
12495
13338
  projectName,
12496
13339
  workingDir: workingDirChip,
@@ -12501,6 +13344,7 @@ User message:
12501
13344
  eternalStage: state.eternalStage,
12502
13345
  goalSummary: state.goalSummary,
12503
13346
  indexState,
13347
+ breakerCountdown,
12504
13348
  modeLabel: liveModeLabel || void 0,
12505
13349
  debugStreamStats: state.debugStreamStats,
12506
13350
  enhanceCountdown,
@@ -12509,7 +13353,7 @@ User message:
12509
13353
  autoProceedCountdown: state.countdown?.remainingSeconds ?? null,
12510
13354
  sessionCount,
12511
13355
  mailbox: mailboxStatus,
12512
- tokenSavingMode: getSettings ? getSettings().featureTokenSaving : tokenSavingMode,
13356
+ tokenSavingMode: getSettings ? getSettings().featureTokenSaving !== "off" : tokenSavingMode,
12513
13357
  toolCount
12514
13358
  }
12515
13359
  ) }),
@@ -12539,8 +13383,7 @@ User message:
12539
13383
  phases: state.autoPhase.phases,
12540
13384
  runningPhaseIds: state.autoPhase.runningPhaseIds,
12541
13385
  elapsedMs: state.autoPhase.elapsedMs,
12542
- nowTick,
12543
- onClose: () => dispatch({ type: "autoPhaseMonitorToggle" })
13386
+ nowTick
12544
13387
  }
12545
13388
  ) : state.worktreeMonitorOpen ? /* @__PURE__ */ jsx(
12546
13389
  WorktreeMonitor,
@@ -12580,7 +13423,23 @@ User message:
12580
13423
  Object.keys(state.worktrees).length > 0 && !state.worktreeMonitorOpen && !state.monitorOpen ? /* @__PURE__ */ jsx(WorktreePanel, { worktrees: state.worktrees, nowTick }) : null,
12581
13424
  state.queuePanelOpen ? /* @__PURE__ */ jsx(QueuePanel, { items: state.queue }) : null,
12582
13425
  state.processListOpen ? /* @__PURE__ */ jsx(ProcessListMonitor, {}) : null,
12583
- 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,
12584
13443
  (() => {
12585
13444
  const anyMonitorOpen = state.agentsMonitorOpen || (state.autoPhase?.monitorOpen ?? false) || state.worktreeMonitorOpen || state.todosMonitorOpen || state.monitorOpen || state.processListOpen || state.queuePanelOpen || state.goalPanelOpen;
12586
13445
  let nextPanelHint;
@@ -12756,13 +13615,30 @@ async function runTui(opts) {
12756
13615
  const mouseEnabled = opts.mouse ?? opts.getSettings?.().mouseMode ?? process.env.WRONGSTACK_MOUSE === "1";
12757
13616
  stdout.write("\x1B[2J\x1B[H");
12758
13617
  const inkStdin = stdin;
12759
- const stopTitle = opts.titleAnimation !== false ? startTerminalTitle({
12760
- stdout,
12761
- events: opts.events,
12762
- model: opts.model,
12763
- appName: opts.projectRoot ? path4.basename(opts.projectRoot) : void 0
12764
- }) : (() => {
12765
- });
13618
+ let titleStop = null;
13619
+ const startTitle = () => {
13620
+ if (titleStop) return;
13621
+ titleStop = startTerminalTitle({
13622
+ stdout,
13623
+ events: opts.events,
13624
+ model: opts.model,
13625
+ appName: opts.projectRoot ? path5.basename(opts.projectRoot) : void 0
13626
+ });
13627
+ };
13628
+ const stopTitle = () => {
13629
+ try {
13630
+ titleStop?.();
13631
+ } catch {
13632
+ }
13633
+ titleStop = null;
13634
+ };
13635
+ const titleController = {
13636
+ setEnabled(on) {
13637
+ if (on) startTitle();
13638
+ else stopTitle();
13639
+ }
13640
+ };
13641
+ if (opts.titleAnimation !== false) startTitle();
12766
13642
  const swallowSignals = ["SIGTSTP", "SIGQUIT", "SIGTTIN", "SIGTTOU"];
12767
13643
  const swallow = () => {
12768
13644
  };
@@ -12816,7 +13692,7 @@ async function runTui(opts) {
12816
13692
  await mailbox.registerClient({
12817
13693
  clientId,
12818
13694
  sessionId: opts.projectRoot,
12819
- name: `TUI [${path4.basename(opts.projectRoot)}]`,
13695
+ name: `TUI [${path5.basename(opts.projectRoot)}]`,
12820
13696
  source: "tui",
12821
13697
  pid: process.pid
12822
13698
  });
@@ -12941,6 +13817,7 @@ async function runTui(opts) {
12941
13817
  setSuggestions: opts.setSuggestions,
12942
13818
  chime: opts.chime,
12943
13819
  confirmExit: opts.confirmExit,
13820
+ titleController,
12944
13821
  mouse: mouseEnabled,
12945
13822
  modeLabel: opts.modeLabel,
12946
13823
  tokenSavingMode: opts.tokenSavingMode,
@@ -12957,7 +13834,10 @@ async function runTui(opts) {
12957
13834
  requestExit: opts.requestExit,
12958
13835
  getLiveSessions: opts.getLiveSessions,
12959
13836
  onSwitchToSession: opts.onSwitchToSession,
12960
- initialAgentsMonitorOpen: opts.initialAgentsMonitorOpen
13837
+ initialAgentsMonitorOpen: opts.initialAgentsMonitorOpen,
13838
+ subscribeCoordinatorEvents: opts.subscribeCoordinatorEvents,
13839
+ onCoordinatorStart: opts.onCoordinatorStart,
13840
+ onCoordinatorStop: opts.onCoordinatorStop
12961
13841
  }),
12962
13842
  { exitOnCtrlC: false, stdin: inkStdin }
12963
13843
  );