@wrongstack/tui 0.5.5 → 0.5.7

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
@@ -4,6 +4,7 @@ import * as fs2 from 'fs/promises';
4
4
  import * as path2 from 'path';
5
5
  import { InputBuilder, DefaultSessionRewinder, formatTodosList, buildChildEnv } from '@wrongstack/core';
6
6
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
7
+ import { getProcessRegistry } from '@wrongstack/tools';
7
8
  import { readClipboardImage } from '@wrongstack/runtime/clipboard';
8
9
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
10
  import { spawn } from 'child_process';
@@ -497,21 +498,13 @@ function History({ entries, streamingText, toolStream }) {
497
498
  const toolTail = toolStream?.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
498
499
  return /* @__PURE__ */ jsxs(Fragment, { children: [
499
500
  /* @__PURE__ */ jsx(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth }) }, entry.id) }),
500
- tail ? /* @__PURE__ */ jsxs(
501
- Box,
502
- {
503
- flexDirection: "column",
504
- borderStyle: "round",
505
- borderColor: "blue",
506
- paddingX: 2,
507
- paddingY: 0,
508
- marginY: 1,
509
- children: [
510
- /* @__PURE__ */ jsx(Box, { flexDirection: "row", marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: " ASISTANT (streaming...) " }) }),
511
- /* @__PURE__ */ jsx(Text, { children: tail })
512
- ]
513
- }
514
- ) : null,
501
+ tail ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
502
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
503
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "ASSISTANT: " }),
504
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(streaming...)" })
505
+ ] }),
506
+ /* @__PURE__ */ jsx(Text, { color: "white", children: tail })
507
+ ] }) : null,
515
508
  toolTail ? /* @__PURE__ */ jsx(
516
509
  ToolStreamBox,
517
510
  {
@@ -614,21 +607,10 @@ function Entry({
614
607
  entry.queued ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (queued)" }) : null
615
608
  ] });
616
609
  case "assistant":
617
- return /* @__PURE__ */ jsxs(
618
- Box,
619
- {
620
- flexDirection: "column",
621
- borderStyle: "round",
622
- borderColor: "blue",
623
- paddingX: 2,
624
- paddingY: 0,
625
- marginY: 1,
626
- children: [
627
- /* @__PURE__ */ jsx(Box, { flexDirection: "row", marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: " ASISTANT " }) }),
628
- /* @__PURE__ */ jsx(Text, { children: renderMarkdownTables(entry.text, termWidth) })
629
- ]
630
- }
631
- );
610
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
611
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "ASSISTANT: " }) }),
612
+ /* @__PURE__ */ jsx(Text, { color: "white", children: renderMarkdownTables(entry.text, termWidth) })
613
+ ] });
632
614
  case "tool": {
633
615
  const argSummary = formatToolArgs(entry.name, entry.input);
634
616
  const outLines = formatToolOutput(
@@ -1649,7 +1631,8 @@ function StatusBar({
1649
1631
  git,
1650
1632
  subagentCount = 0,
1651
1633
  context,
1652
- projectName
1634
+ projectName,
1635
+ processCount
1653
1636
  }) {
1654
1637
  const usage = tokenCounter?.total();
1655
1638
  const cost = tokenCounter?.estimateCost();
@@ -1714,6 +1697,15 @@ function StatusBar({
1714
1697
  queueCount
1715
1698
  ] })
1716
1699
  ] }) : null,
1700
+ typeof processCount === "number" && processCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1701
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1702
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1703
+ "\u26A1 ",
1704
+ processCount,
1705
+ " process",
1706
+ processCount === 1 ? "" : "es"
1707
+ ] })
1708
+ ] }) : null,
1717
1709
  hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
1718
1710
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1719
1711
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
@@ -2111,6 +2103,101 @@ function oneLine(s, max) {
2111
2103
  const collapsed = s.replace(/\s+/g, " ").trim();
2112
2104
  return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
2113
2105
  }
2106
+ var USAGE2 = "Usage:\n /kill \u2014 list active processes + breaker state\n /kill list \u2014 same as /kill\n /kill all \u2014 kill all tracked processes (SIGTERM \u2192 SIGKILL)\n /kill force \u2014 kill all with SIGKILL immediately\n /kill reset \u2014 reset the circuit breaker to closed\n /kill <pid> \u2014 kill a specific process by PID";
2107
+ function createKillSlashCommand() {
2108
+ return {
2109
+ name: "kill",
2110
+ description: "List or kill active bash/exec processes managed by the process registry.",
2111
+ async run(args) {
2112
+ const trimmed = args.trim();
2113
+ const parts = trimmed.split(/\s+/);
2114
+ const sub = parts[0]?.toLowerCase() ?? "";
2115
+ if (sub === "" || sub === "list") {
2116
+ return { message: renderList2() };
2117
+ }
2118
+ if (sub === "all") {
2119
+ const pids = getProcessRegistry().killAll();
2120
+ if (pids.length === 0) return { message: "No processes to kill." };
2121
+ return { message: `Killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}` };
2122
+ }
2123
+ if (sub === "force") {
2124
+ getProcessRegistry().forceBreakerOpen();
2125
+ const pids = getProcessRegistry().killAll({ force: true });
2126
+ if (pids.length === 0) return { message: "Circuit breaker forced open. No processes to kill." };
2127
+ return { message: `Force-killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}` };
2128
+ }
2129
+ if (sub === "reset") {
2130
+ getProcessRegistry().forceBreakerReset();
2131
+ return { message: "Circuit breaker reset to closed. Bash/exec calls allowed." };
2132
+ }
2133
+ const pid = Number.parseInt(sub, 10);
2134
+ if (!Number.isNaN(pid) && pid > 0) {
2135
+ const found = getProcessRegistry().kill(pid);
2136
+ if (found) return { message: `Killed process ${pid}.` };
2137
+ return { message: `Process ${pid} not found in registry.` };
2138
+ }
2139
+ return { message: `Unknown subcommand "${sub}".
2140
+ ${USAGE2}` };
2141
+ }
2142
+ };
2143
+ }
2144
+ function renderList2() {
2145
+ const registry = getProcessRegistry();
2146
+ const stats = registry.stats();
2147
+ const all = registry.list();
2148
+ const breaker = stats.breaker;
2149
+ const stateLabel = breaker.state === "closed" ? "\u{1F7E2} closed" : breaker.state === "half-open" ? "\u{1F7E1} half-open" : `\u{1F534} open (cooldown ${breaker.cooldownRemainingMs !== null ? `${(breaker.cooldownRemainingMs / 1e3).toFixed(0)}s` : "\u2014"})`;
2150
+ const breakerLine = [
2151
+ ` Circuit breaker: ${stateLabel}`,
2152
+ ` consecutive failures: ${breaker.consecutiveFailures}/5`,
2153
+ ` slow calls in window: ${breaker.slowCallsInWindow}/3`,
2154
+ ` calls in window: ${breaker.callsInWindow}/30`
2155
+ ].join("\n");
2156
+ if (all.length === 0) {
2157
+ return `No active processes.
2158
+
2159
+ ${breakerLine}`;
2160
+ }
2161
+ const now = Date.now();
2162
+ const lines = [`Active processes (${all.length}):`];
2163
+ for (const p of all) {
2164
+ const age = ((now - p.startedAt) / 1e3).toFixed(1);
2165
+ const killedTag = p.killed ? " [killed]" : "";
2166
+ const cmd = p.command.length > 80 ? p.command.slice(0, 77) + "\u2026" : p.command;
2167
+ lines.push(` ${p.pid} ${p.name} ${age}s ${cmd}${killedTag}`);
2168
+ }
2169
+ return [...lines, "", breakerLine].join("\n");
2170
+ }
2171
+ function createPsSlashCommand() {
2172
+ return {
2173
+ name: "ps",
2174
+ description: "List all active bash/exec processes tracked by the process registry.",
2175
+ async run(_args) {
2176
+ return { message: renderList3() };
2177
+ }
2178
+ };
2179
+ }
2180
+ function renderList3() {
2181
+ const registry = getProcessRegistry();
2182
+ const stats = registry.stats();
2183
+ const all = registry.list();
2184
+ if (all.length === 0) return "No active processes.";
2185
+ const breaker = stats.breaker;
2186
+ const stateLabel = breaker.state === "closed" ? "\u{1F7E2} closed" : breaker.state === "half-open" ? "\u{1F7E1} half-open" : `\u{1F534} open`;
2187
+ const now = Date.now();
2188
+ const lines = [
2189
+ `Active processes (${all.length}) \u2014 breaker ${stateLabel}`,
2190
+ ` failure=${breaker.consecutiveFailures}/5 slow=${breaker.slowCallsInWindow}/3 rate=${breaker.callsInWindow}/30`,
2191
+ ""
2192
+ ];
2193
+ for (const p of all) {
2194
+ const age = ((now - p.startedAt) / 1e3).toFixed(1);
2195
+ const killedTag = p.killed ? " [killed]" : "";
2196
+ const cmd = p.command.length > 80 ? p.command.slice(0, 77) + "\u2026" : p.command;
2197
+ lines.push(` ${p.pid} ${p.name} ${age}s ${cmd}${killedTag}`);
2198
+ }
2199
+ return lines.join("\n");
2200
+ }
2114
2201
  function selectedSlashCommandLine(picker) {
2115
2202
  if (!picker.open || picker.matches.length === 0) return null;
2116
2203
  const picked = picker.matches[picker.selected];
@@ -3091,6 +3178,19 @@ function App({
3091
3178
  slashRegistry.unregister("queue");
3092
3179
  };
3093
3180
  }, [slashRegistry]);
3181
+ useEffect(() => {
3182
+ slashRegistry.register(createKillSlashCommand());
3183
+ slashRegistry.register(createPsSlashCommand());
3184
+ return () => {
3185
+ slashRegistry.unregister("kill");
3186
+ slashRegistry.unregister("ps");
3187
+ };
3188
+ }, [slashRegistry]);
3189
+ useEffect(() => {
3190
+ return () => {
3191
+ getProcessRegistry().killAll();
3192
+ };
3193
+ }, []);
3094
3194
  useEffect(() => {
3095
3195
  const ALT_OFF = "\x1B[?1049l";
3096
3196
  const ALT_ON = "\x1B[?1049h";
@@ -3670,6 +3770,7 @@ function App({
3670
3770
  const onSigint = () => {
3671
3771
  const current = stateRef.current;
3672
3772
  if (current.interrupts >= 1) {
3773
+ getProcessRegistry().killAll({ force: true });
3673
3774
  if (current.interrupts >= 2) {
3674
3775
  process.exit(130);
3675
3776
  }
@@ -3707,6 +3808,8 @@ function App({
3707
3808
  });
3708
3809
  void Promise.race([director.terminateAll().catch(() => void 0), cap]);
3709
3810
  }
3811
+ const killed = getProcessRegistry().killAll();
3812
+ const procTag = killed.length > 0 ? ` + killed ${killed.length} process${killed.length === 1 ? "" : "es"}` : "";
3710
3813
  const droppedCount = stateRef.current.queue.length;
3711
3814
  if (droppedCount > 0) {
3712
3815
  dispatch({ type: "queueClear" });
@@ -3714,7 +3817,7 @@ function App({
3714
3817
  type: "addEntry",
3715
3818
  entry: {
3716
3819
  kind: "warn",
3717
- text: `Iteration cancelled${director ? " + fleet terminated" : ""}. Dropped ${droppedCount} queued message${droppedCount === 1 ? "" : "s"}. Press Ctrl+C again to exit.`
3820
+ text: `Iteration cancelled${director ? " + fleet terminated" : ""}${procTag}. Dropped ${droppedCount} queued message${droppedCount === 1 ? "" : "s"}. Press Ctrl+C again to exit.`
3718
3821
  }
3719
3822
  });
3720
3823
  } else {
@@ -3722,14 +3825,16 @@ function App({
3722
3825
  type: "addEntry",
3723
3826
  entry: {
3724
3827
  kind: "warn",
3725
- text: `Iteration cancelled${director ? " + fleet terminated" : ""}. Press Ctrl+C again to exit.`
3828
+ text: `Iteration cancelled${director ? " + fleet terminated" : ""}${procTag}. Press Ctrl+C again to exit.`
3726
3829
  }
3727
3830
  });
3728
3831
  }
3729
3832
  } else {
3833
+ const killed = getProcessRegistry().killAll();
3834
+ const procTag = killed.length > 0 ? ` Killed ${killed.length} process${killed.length === 1 ? "" : "es"}.` : "";
3730
3835
  dispatch({
3731
3836
  type: "addEntry",
3732
- entry: { kind: "warn", text: "Press Ctrl+C again to exit." }
3837
+ entry: { kind: "warn", text: `Press Ctrl+C again to exit.${procTag}` }
3733
3838
  });
3734
3839
  }
3735
3840
  };
@@ -4344,7 +4449,8 @@ User message:
4344
4449
  git: gitInfo,
4345
4450
  context: contextWindow,
4346
4451
  projectName,
4347
- subagentCount: Object.keys(state.fleet).length
4452
+ subagentCount: Object.keys(state.fleet).length,
4453
+ processCount: getProcessRegistry().activeCount
4348
4454
  }
4349
4455
  ),
4350
4456
  director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null