@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 +141 -35
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
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:
|
|
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
|