@wrongstack/tui 0.5.6 → 0.6.0
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.d.ts +57 -2
- package/dist/index.js +205 -45
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as _wrongstack_core from '@wrongstack/core';
|
|
1
2
|
import { Agent, SlashCommandRegistry, AttachmentStore, EventBus, TokenCounter, QueueStore, Director } from '@wrongstack/core';
|
|
2
3
|
import { VisionAdapters } from '@wrongstack/runtime/vision';
|
|
3
4
|
import React from 'react';
|
|
@@ -29,7 +30,19 @@ interface RunTuiOptions {
|
|
|
29
30
|
/** Query live YOLO state from the permission policy. */
|
|
30
31
|
getYolo?: () => boolean;
|
|
31
32
|
/** Query the live autonomy mode. */
|
|
32
|
-
getAutonomy?: () => 'off' | 'suggest' | 'auto';
|
|
33
|
+
getAutonomy?: () => 'off' | 'suggest' | 'auto' | 'eternal';
|
|
34
|
+
/**
|
|
35
|
+
* Access the eternal-autonomy engine. When autonomy mode flips to
|
|
36
|
+
* 'eternal' the TUI drives `runOneIteration()` from the post-slash hook
|
|
37
|
+
* so the engine and TUI never race for the shared Context.
|
|
38
|
+
*/
|
|
39
|
+
getEternalEngine?: () => _wrongstack_core.EternalAutonomyEngine | null;
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to live per-iteration events from the eternal engine.
|
|
42
|
+
* Returns an unsubscribe function. TUI uses this to render each
|
|
43
|
+
* iteration as a live timeline entry as it lands.
|
|
44
|
+
*/
|
|
45
|
+
subscribeEternalIteration?: (fn: (entry: _wrongstack_core.JournalEntry) => void) => () => void;
|
|
33
46
|
/** Renders in the startup banner. Read from the CLI's package.json. */
|
|
34
47
|
appVersion?: string;
|
|
35
48
|
/** Provider id for the startup banner ("openai", "anthropic", ...). */
|
|
@@ -88,6 +101,12 @@ interface RunTuiOptions {
|
|
|
88
101
|
fleetRoster?: Record<string, {
|
|
89
102
|
name: string;
|
|
90
103
|
}>;
|
|
104
|
+
/**
|
|
105
|
+
* Shared controller for the `/fleet stream on|off` toggle. The slash
|
|
106
|
+
* command runs in the CLI process and needs to flip TUI reducer state;
|
|
107
|
+
* the App installs a dispatch-backed `setEnabled` here on mount so
|
|
108
|
+
* both sides stay synchronized.
|
|
109
|
+
*/
|
|
91
110
|
/**
|
|
92
111
|
* Shared controller for the `/fleet stream on|off` toggle. The slash
|
|
93
112
|
* command runs in the CLI process and needs to flip TUI reducer state;
|
|
@@ -98,6 +117,14 @@ interface RunTuiOptions {
|
|
|
98
117
|
enabled: boolean;
|
|
99
118
|
setEnabled: (enabled: boolean) => void;
|
|
100
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* Controller for status bar hidden items. App installs a dispatch-backed
|
|
122
|
+
* setter on mount so the /statusline slash command can update the TUI's
|
|
123
|
+
* visible bar without a round-trip. The initial value is loaded from
|
|
124
|
+
* the config file before App mounts.
|
|
125
|
+
*/
|
|
126
|
+
statuslineHiddenItems: Array<'todos' | 'plan' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>;
|
|
127
|
+
setStatuslineHiddenItems: (items: Array<'todos' | 'plan' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>) => void;
|
|
101
128
|
/**
|
|
102
129
|
* If set, the App boots straight into goal mode — the text is wrapped
|
|
103
130
|
* in the GOAL preamble and submitted as the first turn. Lets users
|
|
@@ -133,4 +160,32 @@ interface RunTuiOptions {
|
|
|
133
160
|
}
|
|
134
161
|
declare function runTui(opts: RunTuiOptions): Promise<number>;
|
|
135
162
|
|
|
136
|
-
|
|
163
|
+
/**
|
|
164
|
+
* `/goal <description>` preamble — the "no force can stop this" mode.
|
|
165
|
+
*
|
|
166
|
+
* Unlike STEERING (which redirects mid-flight), GOAL is a contract:
|
|
167
|
+
* the user hands over a problem, the agent commits to verifiably
|
|
168
|
+
* finishing it, and every iteration re-reads this preamble from the
|
|
169
|
+
* conversation history. The hardening is entirely prompt-level —
|
|
170
|
+
* the system has already removed implicit budget caps, so this
|
|
171
|
+
* preamble's job is to remove the MODEL's tendency to hedge, ask
|
|
172
|
+
* permission, or declare premature success.
|
|
173
|
+
*
|
|
174
|
+
* The four sections are intentional:
|
|
175
|
+
* 1. AUTHORITY — explicit grant of unbounded fan-out + model
|
|
176
|
+
* switching. Without this the model self-throttles ("I shouldn't
|
|
177
|
+
* spawn too many…") even when budgets are unlimited.
|
|
178
|
+
* 2. DONE — concrete bar for completion. Forces a verifiable
|
|
179
|
+
* artifact (test passing, file written, bug re-run clean).
|
|
180
|
+
* Without this the model returns "I believe it's fixed" and
|
|
181
|
+
* counts that as done.
|
|
182
|
+
* 3. NOT DONE — explicit anti-patterns. Each item is something we
|
|
183
|
+
* saw real agents do as a "completion" that wasn't.
|
|
184
|
+
* 4. PERSISTENCE — three-angle rule for blockers. Stops the model
|
|
185
|
+
* from giving up on the first tool failure.
|
|
186
|
+
*
|
|
187
|
+
* Exported for the test that pins the structural guarantees.
|
|
188
|
+
*/
|
|
189
|
+
declare function buildGoalPreamble(goal: string): string;
|
|
190
|
+
|
|
191
|
+
export { type RunTuiOptions, buildGoalPreamble, runTui };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { render, useApp, Box, useStdout, Static, Text, useInput, useStdin } from 'ink';
|
|
2
|
-
import React2, { useState, useReducer, useRef,
|
|
2
|
+
import React2, { useState, useEffect, useReducer, useRef, useMemo } from 'react';
|
|
3
3
|
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,8 +1631,11 @@ function StatusBar({
|
|
|
1630
1631
|
git,
|
|
1631
1632
|
subagentCount = 0,
|
|
1632
1633
|
context,
|
|
1633
|
-
projectName
|
|
1634
|
+
projectName,
|
|
1635
|
+
processCount,
|
|
1636
|
+
hiddenItems
|
|
1634
1637
|
}) {
|
|
1638
|
+
const hiddenSet = new Set(hiddenItems);
|
|
1635
1639
|
const usage = tokenCounter?.total();
|
|
1636
1640
|
const cost = tokenCounter?.estimateCost();
|
|
1637
1641
|
const cache2 = tokenCounter?.cacheStats();
|
|
@@ -1695,6 +1699,15 @@ function StatusBar({
|
|
|
1695
1699
|
queueCount
|
|
1696
1700
|
] })
|
|
1697
1701
|
] }) : null,
|
|
1702
|
+
typeof processCount === "number" && processCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1703
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1704
|
+
/* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
1705
|
+
"\u26A1 ",
|
|
1706
|
+
processCount,
|
|
1707
|
+
" process",
|
|
1708
|
+
processCount === 1 ? "" : "es"
|
|
1709
|
+
] })
|
|
1710
|
+
] }) : null,
|
|
1698
1711
|
hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1699
1712
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1700
1713
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
|
|
@@ -1704,12 +1717,19 @@ function StatusBar({
|
|
|
1704
1717
|
yolo ? /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "\u26A0 YOLO" }) : null,
|
|
1705
1718
|
autonomy && autonomy !== "off" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1706
1719
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1707
|
-
/* @__PURE__ */ jsxs(
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1720
|
+
/* @__PURE__ */ jsxs(
|
|
1721
|
+
Text,
|
|
1722
|
+
{
|
|
1723
|
+
color: autonomy === "eternal" ? "red" : autonomy === "auto" ? "yellow" : "cyan",
|
|
1724
|
+
bold: true,
|
|
1725
|
+
children: [
|
|
1726
|
+
"\u221E ",
|
|
1727
|
+
autonomy.toUpperCase()
|
|
1728
|
+
]
|
|
1729
|
+
}
|
|
1730
|
+
)
|
|
1711
1731
|
] }) : null,
|
|
1712
|
-
elapsedMs !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1732
|
+
elapsedMs !== void 0 && !hiddenSet.has("elapsed") ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1713
1733
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1714
1734
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1715
1735
|
"\u23F1 ",
|
|
@@ -2092,6 +2112,101 @@ function oneLine(s, max) {
|
|
|
2092
2112
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
2093
2113
|
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
2094
2114
|
}
|
|
2115
|
+
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";
|
|
2116
|
+
function createKillSlashCommand() {
|
|
2117
|
+
return {
|
|
2118
|
+
name: "kill",
|
|
2119
|
+
description: "List or kill active bash/exec processes managed by the process registry.",
|
|
2120
|
+
async run(args) {
|
|
2121
|
+
const trimmed = args.trim();
|
|
2122
|
+
const parts = trimmed.split(/\s+/);
|
|
2123
|
+
const sub = parts[0]?.toLowerCase() ?? "";
|
|
2124
|
+
if (sub === "" || sub === "list") {
|
|
2125
|
+
return { message: renderList2() };
|
|
2126
|
+
}
|
|
2127
|
+
if (sub === "all") {
|
|
2128
|
+
const pids = getProcessRegistry().killAll();
|
|
2129
|
+
if (pids.length === 0) return { message: "No processes to kill." };
|
|
2130
|
+
return { message: `Killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}` };
|
|
2131
|
+
}
|
|
2132
|
+
if (sub === "force") {
|
|
2133
|
+
getProcessRegistry().forceBreakerOpen();
|
|
2134
|
+
const pids = getProcessRegistry().killAll({ force: true });
|
|
2135
|
+
if (pids.length === 0) return { message: "Circuit breaker forced open. No processes to kill." };
|
|
2136
|
+
return { message: `Force-killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}` };
|
|
2137
|
+
}
|
|
2138
|
+
if (sub === "reset") {
|
|
2139
|
+
getProcessRegistry().forceBreakerReset();
|
|
2140
|
+
return { message: "Circuit breaker reset to closed. Bash/exec calls allowed." };
|
|
2141
|
+
}
|
|
2142
|
+
const pid = Number.parseInt(sub, 10);
|
|
2143
|
+
if (!Number.isNaN(pid) && pid > 0) {
|
|
2144
|
+
const found = getProcessRegistry().kill(pid);
|
|
2145
|
+
if (found) return { message: `Killed process ${pid}.` };
|
|
2146
|
+
return { message: `Process ${pid} not found in registry.` };
|
|
2147
|
+
}
|
|
2148
|
+
return { message: `Unknown subcommand "${sub}".
|
|
2149
|
+
${USAGE2}` };
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
function renderList2() {
|
|
2154
|
+
const registry = getProcessRegistry();
|
|
2155
|
+
const stats = registry.stats();
|
|
2156
|
+
const all = registry.list();
|
|
2157
|
+
const breaker = stats.breaker;
|
|
2158
|
+
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"})`;
|
|
2159
|
+
const breakerLine = [
|
|
2160
|
+
` Circuit breaker: ${stateLabel}`,
|
|
2161
|
+
` consecutive failures: ${breaker.consecutiveFailures}/5`,
|
|
2162
|
+
` slow calls in window: ${breaker.slowCallsInWindow}/3`,
|
|
2163
|
+
` calls in window: ${breaker.callsInWindow}/30`
|
|
2164
|
+
].join("\n");
|
|
2165
|
+
if (all.length === 0) {
|
|
2166
|
+
return `No active processes.
|
|
2167
|
+
|
|
2168
|
+
${breakerLine}`;
|
|
2169
|
+
}
|
|
2170
|
+
const now = Date.now();
|
|
2171
|
+
const lines = [`Active processes (${all.length}):`];
|
|
2172
|
+
for (const p of all) {
|
|
2173
|
+
const age = ((now - p.startedAt) / 1e3).toFixed(1);
|
|
2174
|
+
const killedTag = p.killed ? " [killed]" : "";
|
|
2175
|
+
const cmd = p.command.length > 80 ? p.command.slice(0, 77) + "\u2026" : p.command;
|
|
2176
|
+
lines.push(` ${p.pid} ${p.name} ${age}s ${cmd}${killedTag}`);
|
|
2177
|
+
}
|
|
2178
|
+
return [...lines, "", breakerLine].join("\n");
|
|
2179
|
+
}
|
|
2180
|
+
function createPsSlashCommand() {
|
|
2181
|
+
return {
|
|
2182
|
+
name: "ps",
|
|
2183
|
+
description: "List all active bash/exec processes tracked by the process registry.",
|
|
2184
|
+
async run(_args) {
|
|
2185
|
+
return { message: renderList3() };
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
function renderList3() {
|
|
2190
|
+
const registry = getProcessRegistry();
|
|
2191
|
+
const stats = registry.stats();
|
|
2192
|
+
const all = registry.list();
|
|
2193
|
+
if (all.length === 0) return "No active processes.";
|
|
2194
|
+
const breaker = stats.breaker;
|
|
2195
|
+
const stateLabel = breaker.state === "closed" ? "\u{1F7E2} closed" : breaker.state === "half-open" ? "\u{1F7E1} half-open" : `\u{1F534} open`;
|
|
2196
|
+
const now = Date.now();
|
|
2197
|
+
const lines = [
|
|
2198
|
+
`Active processes (${all.length}) \u2014 breaker ${stateLabel}`,
|
|
2199
|
+
` failure=${breaker.consecutiveFailures}/5 slow=${breaker.slowCallsInWindow}/3 rate=${breaker.callsInWindow}/30`,
|
|
2200
|
+
""
|
|
2201
|
+
];
|
|
2202
|
+
for (const p of all) {
|
|
2203
|
+
const age = ((now - p.startedAt) / 1e3).toFixed(1);
|
|
2204
|
+
const killedTag = p.killed ? " [killed]" : "";
|
|
2205
|
+
const cmd = p.command.length > 80 ? p.command.slice(0, 77) + "\u2026" : p.command;
|
|
2206
|
+
lines.push(` ${p.pid} ${p.name} ${age}s ${cmd}${killedTag}`);
|
|
2207
|
+
}
|
|
2208
|
+
return lines.join("\n");
|
|
2209
|
+
}
|
|
2095
2210
|
function selectedSlashCommandLine(picker) {
|
|
2096
2211
|
if (!picker.open || picker.matches.length === 0) return null;
|
|
2097
2212
|
const picked = picker.matches[picker.selected];
|
|
@@ -2670,6 +2785,8 @@ function App({
|
|
|
2670
2785
|
yolo = false,
|
|
2671
2786
|
getYolo,
|
|
2672
2787
|
getAutonomy,
|
|
2788
|
+
getEternalEngine,
|
|
2789
|
+
subscribeEternalIteration,
|
|
2673
2790
|
getSDDContext,
|
|
2674
2791
|
onSDDOutput,
|
|
2675
2792
|
appVersion,
|
|
@@ -2684,6 +2801,8 @@ function App({
|
|
|
2684
2801
|
fleetRoster,
|
|
2685
2802
|
onClearHistory,
|
|
2686
2803
|
fleetStreamController,
|
|
2804
|
+
statuslineHiddenItems,
|
|
2805
|
+
setStatuslineHiddenItems,
|
|
2687
2806
|
initialGoal,
|
|
2688
2807
|
initialAsk,
|
|
2689
2808
|
sessionsDir
|
|
@@ -2693,6 +2812,13 @@ function App({
|
|
|
2693
2812
|
const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
|
|
2694
2813
|
const [yoloLive, setYoloLive] = useState(yolo);
|
|
2695
2814
|
const [autonomyLive, setAutonomyLive] = useState(getAutonomy?.() ?? "off");
|
|
2815
|
+
const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
|
|
2816
|
+
useEffect(() => {
|
|
2817
|
+
setHiddenItems(statuslineHiddenItems);
|
|
2818
|
+
}, [statuslineHiddenItems]);
|
|
2819
|
+
useEffect(() => {
|
|
2820
|
+
setStatuslineHiddenItems(hiddenItems);
|
|
2821
|
+
}, [setStatuslineHiddenItems]);
|
|
2696
2822
|
const [state, dispatch] = useReducer(reducer, {
|
|
2697
2823
|
entries: banner ? [
|
|
2698
2824
|
{
|
|
@@ -3072,6 +3198,19 @@ function App({
|
|
|
3072
3198
|
slashRegistry.unregister("queue");
|
|
3073
3199
|
};
|
|
3074
3200
|
}, [slashRegistry]);
|
|
3201
|
+
useEffect(() => {
|
|
3202
|
+
slashRegistry.register(createKillSlashCommand());
|
|
3203
|
+
slashRegistry.register(createPsSlashCommand());
|
|
3204
|
+
return () => {
|
|
3205
|
+
slashRegistry.unregister("kill");
|
|
3206
|
+
slashRegistry.unregister("ps");
|
|
3207
|
+
};
|
|
3208
|
+
}, [slashRegistry]);
|
|
3209
|
+
useEffect(() => {
|
|
3210
|
+
return () => {
|
|
3211
|
+
getProcessRegistry().killAll();
|
|
3212
|
+
};
|
|
3213
|
+
}, []);
|
|
3075
3214
|
useEffect(() => {
|
|
3076
3215
|
const ALT_OFF = "\x1B[?1049l";
|
|
3077
3216
|
const ALT_ON = "\x1B[?1049h";
|
|
@@ -3194,39 +3333,6 @@ function App({
|
|
|
3194
3333
|
slashRegistry.unregister("rewind");
|
|
3195
3334
|
};
|
|
3196
3335
|
}, [slashRegistry, handleRewindTo]);
|
|
3197
|
-
useEffect(() => {
|
|
3198
|
-
const cmd = {
|
|
3199
|
-
name: "goal",
|
|
3200
|
-
description: "Lock in a goal \u2014 no budgets, no hedging, no premature done. /goal <description>",
|
|
3201
|
-
help: [
|
|
3202
|
-
"Usage: /goal <description>",
|
|
3203
|
-
"",
|
|
3204
|
-
"Hands the agent a task it must drive to a verifiable finish.",
|
|
3205
|
-
"Adds a preamble to the next turn that grants full autonomy",
|
|
3206
|
-
"(unlimited subagents, any provider/model, retry-until-it-works),",
|
|
3207
|
-
'spells out what "done" actually means, and forbids hedge-style',
|
|
3208
|
-
'completions ("I believe this works", "should I continue?").',
|
|
3209
|
-
"",
|
|
3210
|
-
"Combine with /steer to redirect mid-goal, or Ctrl+C / /fleet kill",
|
|
3211
|
-
"to bail out \u2014 only the user can stop a /goal."
|
|
3212
|
-
].join("\n"),
|
|
3213
|
-
async run(args) {
|
|
3214
|
-
const goal = args.trim();
|
|
3215
|
-
if (!goal) return { message: "Usage: /goal <description>" };
|
|
3216
|
-
const preamble = buildGoalPreamble(goal);
|
|
3217
|
-
const shortGoal = goal.length > 80 ? `${goal.slice(0, 80)}\u2026` : goal;
|
|
3218
|
-
return {
|
|
3219
|
-
message: `\u{1F3AF} Goal locked: ${shortGoal}
|
|
3220
|
-
Agent will work until verifiably complete. Esc / /steer to redirect, Ctrl+C to stop.`,
|
|
3221
|
-
runText: preamble
|
|
3222
|
-
};
|
|
3223
|
-
}
|
|
3224
|
-
};
|
|
3225
|
-
slashRegistry.register(cmd);
|
|
3226
|
-
return () => {
|
|
3227
|
-
slashRegistry.unregister("goal");
|
|
3228
|
-
};
|
|
3229
|
-
}, [slashRegistry]);
|
|
3230
3336
|
useEffect(() => {
|
|
3231
3337
|
if (!getPickableProviders || !switchProviderAndModel) return;
|
|
3232
3338
|
const cmd = {
|
|
@@ -3651,6 +3757,7 @@ function App({
|
|
|
3651
3757
|
const onSigint = () => {
|
|
3652
3758
|
const current = stateRef.current;
|
|
3653
3759
|
if (current.interrupts >= 1) {
|
|
3760
|
+
getProcessRegistry().killAll({ force: true });
|
|
3654
3761
|
if (current.interrupts >= 2) {
|
|
3655
3762
|
process.exit(130);
|
|
3656
3763
|
}
|
|
@@ -3688,6 +3795,8 @@ function App({
|
|
|
3688
3795
|
});
|
|
3689
3796
|
void Promise.race([director.terminateAll().catch(() => void 0), cap]);
|
|
3690
3797
|
}
|
|
3798
|
+
const killed = getProcessRegistry().killAll();
|
|
3799
|
+
const procTag = killed.length > 0 ? ` + killed ${killed.length} process${killed.length === 1 ? "" : "es"}` : "";
|
|
3691
3800
|
const droppedCount = stateRef.current.queue.length;
|
|
3692
3801
|
if (droppedCount > 0) {
|
|
3693
3802
|
dispatch({ type: "queueClear" });
|
|
@@ -3695,7 +3804,7 @@ function App({
|
|
|
3695
3804
|
type: "addEntry",
|
|
3696
3805
|
entry: {
|
|
3697
3806
|
kind: "warn",
|
|
3698
|
-
text: `Iteration cancelled${director ? " + fleet terminated" : ""}. Dropped ${droppedCount} queued message${droppedCount === 1 ? "" : "s"}. Press Ctrl+C again to exit.`
|
|
3807
|
+
text: `Iteration cancelled${director ? " + fleet terminated" : ""}${procTag}. Dropped ${droppedCount} queued message${droppedCount === 1 ? "" : "s"}. Press Ctrl+C again to exit.`
|
|
3699
3808
|
}
|
|
3700
3809
|
});
|
|
3701
3810
|
} else {
|
|
@@ -3703,14 +3812,16 @@ function App({
|
|
|
3703
3812
|
type: "addEntry",
|
|
3704
3813
|
entry: {
|
|
3705
3814
|
kind: "warn",
|
|
3706
|
-
text: `Iteration cancelled${director ? " + fleet terminated" : ""}. Press Ctrl+C again to exit.`
|
|
3815
|
+
text: `Iteration cancelled${director ? " + fleet terminated" : ""}${procTag}. Press Ctrl+C again to exit.`
|
|
3707
3816
|
}
|
|
3708
3817
|
});
|
|
3709
3818
|
}
|
|
3710
3819
|
} else {
|
|
3820
|
+
const killed = getProcessRegistry().killAll();
|
|
3821
|
+
const procTag = killed.length > 0 ? ` Killed ${killed.length} process${killed.length === 1 ? "" : "es"}.` : "";
|
|
3711
3822
|
dispatch({
|
|
3712
3823
|
type: "addEntry",
|
|
3713
|
-
entry: { kind: "warn", text:
|
|
3824
|
+
entry: { kind: "warn", text: `Press Ctrl+C again to exit.${procTag}` }
|
|
3714
3825
|
});
|
|
3715
3826
|
}
|
|
3716
3827
|
};
|
|
@@ -4100,6 +4211,46 @@ function App({
|
|
|
4100
4211
|
};
|
|
4101
4212
|
const runBlocksRef = useRef(runBlocks);
|
|
4102
4213
|
runBlocksRef.current = runBlocks;
|
|
4214
|
+
const runEternalLoop = async () => {
|
|
4215
|
+
const engine = getEternalEngine?.();
|
|
4216
|
+
if (!engine) return;
|
|
4217
|
+
if (eternalLoopRunningRef.current) return;
|
|
4218
|
+
eternalLoopRunningRef.current = true;
|
|
4219
|
+
try {
|
|
4220
|
+
while (true) {
|
|
4221
|
+
const liveMode = getAutonomy?.() ?? "off";
|
|
4222
|
+
if (liveMode !== "eternal") break;
|
|
4223
|
+
if (engine.currentState === "stopped") break;
|
|
4224
|
+
dispatch({ type: "status", status: "running" });
|
|
4225
|
+
try {
|
|
4226
|
+
await engine.runOneIteration();
|
|
4227
|
+
} catch (err) {
|
|
4228
|
+
dispatch({
|
|
4229
|
+
type: "addEntry",
|
|
4230
|
+
entry: { kind: "error", text: `[eternal] ${err instanceof Error ? err.message : String(err)}` }
|
|
4231
|
+
});
|
|
4232
|
+
}
|
|
4233
|
+
dispatch({ type: "status", status: "idle" });
|
|
4234
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
4235
|
+
}
|
|
4236
|
+
} finally {
|
|
4237
|
+
eternalLoopRunningRef.current = false;
|
|
4238
|
+
}
|
|
4239
|
+
};
|
|
4240
|
+
const eternalLoopRunningRef = useRef(false);
|
|
4241
|
+
const runEternalLoopRef = useRef(runEternalLoop);
|
|
4242
|
+
runEternalLoopRef.current = runEternalLoop;
|
|
4243
|
+
useEffect(() => {
|
|
4244
|
+
if (!subscribeEternalIteration) return;
|
|
4245
|
+
const unsub = subscribeEternalIteration((entry) => {
|
|
4246
|
+
const mark = entry.status === "success" ? "\u2713" : entry.status === "failure" ? "\u2717" : entry.status === "aborted" ? "\u2298" : "\xB7";
|
|
4247
|
+
const cost = typeof entry.costUsd === "number" ? ` ($${entry.costUsd.toFixed(4)})` : "";
|
|
4248
|
+
const note = entry.note ? ` \u2014 ${entry.note.slice(0, 80)}` : "";
|
|
4249
|
+
const text = `#${entry.iteration} ${mark} [${entry.source}] ${entry.task}${cost}${note}`;
|
|
4250
|
+
dispatch({ type: "addEntry", entry: { kind: "info", text } });
|
|
4251
|
+
});
|
|
4252
|
+
return unsub;
|
|
4253
|
+
}, [subscribeEternalIteration]);
|
|
4103
4254
|
const submit = async (overrideRaw) => {
|
|
4104
4255
|
const raw = overrideRaw ?? draftRef.current.buffer;
|
|
4105
4256
|
const trimmed = raw.trim();
|
|
@@ -4134,6 +4285,9 @@ function App({
|
|
|
4134
4285
|
if (getAutonomy) {
|
|
4135
4286
|
const currentAutonomy = getAutonomy();
|
|
4136
4287
|
if (currentAutonomy !== autonomyLive) setAutonomyLive(currentAutonomy);
|
|
4288
|
+
if (currentAutonomy === "eternal" && getEternalEngine) {
|
|
4289
|
+
void runEternalLoopRef.current();
|
|
4290
|
+
}
|
|
4137
4291
|
}
|
|
4138
4292
|
if (res?.exit) {
|
|
4139
4293
|
exit();
|
|
@@ -4325,7 +4479,9 @@ User message:
|
|
|
4325
4479
|
git: gitInfo,
|
|
4326
4480
|
context: contextWindow,
|
|
4327
4481
|
projectName,
|
|
4328
|
-
subagentCount: Object.keys(state.fleet).length
|
|
4482
|
+
subagentCount: Object.keys(state.fleet).length,
|
|
4483
|
+
processCount: getProcessRegistry().activeCount,
|
|
4484
|
+
hiddenItems
|
|
4329
4485
|
}
|
|
4330
4486
|
),
|
|
4331
4487
|
director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
|
|
@@ -4453,6 +4609,8 @@ async function runTui(opts) {
|
|
|
4453
4609
|
yolo: opts.yolo,
|
|
4454
4610
|
getYolo: opts.getYolo,
|
|
4455
4611
|
getAutonomy: opts.getAutonomy,
|
|
4612
|
+
getEternalEngine: opts.getEternalEngine,
|
|
4613
|
+
subscribeEternalIteration: opts.subscribeEternalIteration,
|
|
4456
4614
|
appVersion: opts.appVersion,
|
|
4457
4615
|
provider: opts.provider,
|
|
4458
4616
|
family: opts.family,
|
|
@@ -4465,6 +4623,8 @@ async function runTui(opts) {
|
|
|
4465
4623
|
fleetRoster: opts.fleetRoster,
|
|
4466
4624
|
onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory(dispatch) : void 0,
|
|
4467
4625
|
fleetStreamController: opts.fleetStreamController,
|
|
4626
|
+
statuslineHiddenItems: opts.statuslineHiddenItems,
|
|
4627
|
+
setStatuslineHiddenItems: opts.setStatuslineHiddenItems,
|
|
4468
4628
|
initialGoal: opts.initialGoal,
|
|
4469
4629
|
initialAsk: opts.initialAsk,
|
|
4470
4630
|
getSDDContext: opts.getSDDContext,
|
|
@@ -4502,6 +4662,6 @@ async function runTui(opts) {
|
|
|
4502
4662
|
});
|
|
4503
4663
|
}
|
|
4504
4664
|
|
|
4505
|
-
export { runTui };
|
|
4665
|
+
export { buildGoalPreamble, runTui };
|
|
4506
4666
|
//# sourceMappingURL=index.js.map
|
|
4507
4667
|
//# sourceMappingURL=index.js.map
|