@wrongstack/tui 0.5.6 → 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 +130 -5
- 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';
|
|
@@ -1630,7 +1631,8 @@ function StatusBar({
|
|
|
1630
1631
|
git,
|
|
1631
1632
|
subagentCount = 0,
|
|
1632
1633
|
context,
|
|
1633
|
-
projectName
|
|
1634
|
+
projectName,
|
|
1635
|
+
processCount
|
|
1634
1636
|
}) {
|
|
1635
1637
|
const usage = tokenCounter?.total();
|
|
1636
1638
|
const cost = tokenCounter?.estimateCost();
|
|
@@ -1695,6 +1697,15 @@ function StatusBar({
|
|
|
1695
1697
|
queueCount
|
|
1696
1698
|
] })
|
|
1697
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,
|
|
1698
1709
|
hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1699
1710
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1700
1711
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
|
|
@@ -2092,6 +2103,101 @@ function oneLine(s, max) {
|
|
|
2092
2103
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
2093
2104
|
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
2094
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
|
+
}
|
|
2095
2201
|
function selectedSlashCommandLine(picker) {
|
|
2096
2202
|
if (!picker.open || picker.matches.length === 0) return null;
|
|
2097
2203
|
const picked = picker.matches[picker.selected];
|
|
@@ -3072,6 +3178,19 @@ function App({
|
|
|
3072
3178
|
slashRegistry.unregister("queue");
|
|
3073
3179
|
};
|
|
3074
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
|
+
}, []);
|
|
3075
3194
|
useEffect(() => {
|
|
3076
3195
|
const ALT_OFF = "\x1B[?1049l";
|
|
3077
3196
|
const ALT_ON = "\x1B[?1049h";
|
|
@@ -3651,6 +3770,7 @@ function App({
|
|
|
3651
3770
|
const onSigint = () => {
|
|
3652
3771
|
const current = stateRef.current;
|
|
3653
3772
|
if (current.interrupts >= 1) {
|
|
3773
|
+
getProcessRegistry().killAll({ force: true });
|
|
3654
3774
|
if (current.interrupts >= 2) {
|
|
3655
3775
|
process.exit(130);
|
|
3656
3776
|
}
|
|
@@ -3688,6 +3808,8 @@ function App({
|
|
|
3688
3808
|
});
|
|
3689
3809
|
void Promise.race([director.terminateAll().catch(() => void 0), cap]);
|
|
3690
3810
|
}
|
|
3811
|
+
const killed = getProcessRegistry().killAll();
|
|
3812
|
+
const procTag = killed.length > 0 ? ` + killed ${killed.length} process${killed.length === 1 ? "" : "es"}` : "";
|
|
3691
3813
|
const droppedCount = stateRef.current.queue.length;
|
|
3692
3814
|
if (droppedCount > 0) {
|
|
3693
3815
|
dispatch({ type: "queueClear" });
|
|
@@ -3695,7 +3817,7 @@ function App({
|
|
|
3695
3817
|
type: "addEntry",
|
|
3696
3818
|
entry: {
|
|
3697
3819
|
kind: "warn",
|
|
3698
|
-
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.`
|
|
3699
3821
|
}
|
|
3700
3822
|
});
|
|
3701
3823
|
} else {
|
|
@@ -3703,14 +3825,16 @@ function App({
|
|
|
3703
3825
|
type: "addEntry",
|
|
3704
3826
|
entry: {
|
|
3705
3827
|
kind: "warn",
|
|
3706
|
-
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.`
|
|
3707
3829
|
}
|
|
3708
3830
|
});
|
|
3709
3831
|
}
|
|
3710
3832
|
} else {
|
|
3833
|
+
const killed = getProcessRegistry().killAll();
|
|
3834
|
+
const procTag = killed.length > 0 ? ` Killed ${killed.length} process${killed.length === 1 ? "" : "es"}.` : "";
|
|
3711
3835
|
dispatch({
|
|
3712
3836
|
type: "addEntry",
|
|
3713
|
-
entry: { kind: "warn", text:
|
|
3837
|
+
entry: { kind: "warn", text: `Press Ctrl+C again to exit.${procTag}` }
|
|
3714
3838
|
});
|
|
3715
3839
|
}
|
|
3716
3840
|
};
|
|
@@ -4325,7 +4449,8 @@ User message:
|
|
|
4325
4449
|
git: gitInfo,
|
|
4326
4450
|
context: contextWindow,
|
|
4327
4451
|
projectName,
|
|
4328
|
-
subagentCount: Object.keys(state.fleet).length
|
|
4452
|
+
subagentCount: Object.keys(state.fleet).length,
|
|
4453
|
+
processCount: getProcessRegistry().activeCount
|
|
4329
4454
|
}
|
|
4330
4455
|
),
|
|
4331
4456
|
director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
|