@wrongstack/tui 0.5.7 → 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 +77 -42
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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,5 +1,5 @@
|
|
|
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';
|
|
@@ -1632,8 +1632,10 @@ function StatusBar({
|
|
|
1632
1632
|
subagentCount = 0,
|
|
1633
1633
|
context,
|
|
1634
1634
|
projectName,
|
|
1635
|
-
processCount
|
|
1635
|
+
processCount,
|
|
1636
|
+
hiddenItems
|
|
1636
1637
|
}) {
|
|
1638
|
+
const hiddenSet = new Set(hiddenItems);
|
|
1637
1639
|
const usage = tokenCounter?.total();
|
|
1638
1640
|
const cost = tokenCounter?.estimateCost();
|
|
1639
1641
|
const cache2 = tokenCounter?.cacheStats();
|
|
@@ -1715,12 +1717,19 @@ function StatusBar({
|
|
|
1715
1717
|
yolo ? /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "\u26A0 YOLO" }) : null,
|
|
1716
1718
|
autonomy && autonomy !== "off" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1717
1719
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1718
|
-
/* @__PURE__ */ jsxs(
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
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
|
+
)
|
|
1722
1731
|
] }) : null,
|
|
1723
|
-
elapsedMs !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1732
|
+
elapsedMs !== void 0 && !hiddenSet.has("elapsed") ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1724
1733
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1725
1734
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1726
1735
|
"\u23F1 ",
|
|
@@ -2776,6 +2785,8 @@ function App({
|
|
|
2776
2785
|
yolo = false,
|
|
2777
2786
|
getYolo,
|
|
2778
2787
|
getAutonomy,
|
|
2788
|
+
getEternalEngine,
|
|
2789
|
+
subscribeEternalIteration,
|
|
2779
2790
|
getSDDContext,
|
|
2780
2791
|
onSDDOutput,
|
|
2781
2792
|
appVersion,
|
|
@@ -2790,6 +2801,8 @@ function App({
|
|
|
2790
2801
|
fleetRoster,
|
|
2791
2802
|
onClearHistory,
|
|
2792
2803
|
fleetStreamController,
|
|
2804
|
+
statuslineHiddenItems,
|
|
2805
|
+
setStatuslineHiddenItems,
|
|
2793
2806
|
initialGoal,
|
|
2794
2807
|
initialAsk,
|
|
2795
2808
|
sessionsDir
|
|
@@ -2799,6 +2812,13 @@ function App({
|
|
|
2799
2812
|
const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
|
|
2800
2813
|
const [yoloLive, setYoloLive] = useState(yolo);
|
|
2801
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]);
|
|
2802
2822
|
const [state, dispatch] = useReducer(reducer, {
|
|
2803
2823
|
entries: banner ? [
|
|
2804
2824
|
{
|
|
@@ -3313,39 +3333,6 @@ function App({
|
|
|
3313
3333
|
slashRegistry.unregister("rewind");
|
|
3314
3334
|
};
|
|
3315
3335
|
}, [slashRegistry, handleRewindTo]);
|
|
3316
|
-
useEffect(() => {
|
|
3317
|
-
const cmd = {
|
|
3318
|
-
name: "goal",
|
|
3319
|
-
description: "Lock in a goal \u2014 no budgets, no hedging, no premature done. /goal <description>",
|
|
3320
|
-
help: [
|
|
3321
|
-
"Usage: /goal <description>",
|
|
3322
|
-
"",
|
|
3323
|
-
"Hands the agent a task it must drive to a verifiable finish.",
|
|
3324
|
-
"Adds a preamble to the next turn that grants full autonomy",
|
|
3325
|
-
"(unlimited subagents, any provider/model, retry-until-it-works),",
|
|
3326
|
-
'spells out what "done" actually means, and forbids hedge-style',
|
|
3327
|
-
'completions ("I believe this works", "should I continue?").',
|
|
3328
|
-
"",
|
|
3329
|
-
"Combine with /steer to redirect mid-goal, or Ctrl+C / /fleet kill",
|
|
3330
|
-
"to bail out \u2014 only the user can stop a /goal."
|
|
3331
|
-
].join("\n"),
|
|
3332
|
-
async run(args) {
|
|
3333
|
-
const goal = args.trim();
|
|
3334
|
-
if (!goal) return { message: "Usage: /goal <description>" };
|
|
3335
|
-
const preamble = buildGoalPreamble(goal);
|
|
3336
|
-
const shortGoal = goal.length > 80 ? `${goal.slice(0, 80)}\u2026` : goal;
|
|
3337
|
-
return {
|
|
3338
|
-
message: `\u{1F3AF} Goal locked: ${shortGoal}
|
|
3339
|
-
Agent will work until verifiably complete. Esc / /steer to redirect, Ctrl+C to stop.`,
|
|
3340
|
-
runText: preamble
|
|
3341
|
-
};
|
|
3342
|
-
}
|
|
3343
|
-
};
|
|
3344
|
-
slashRegistry.register(cmd);
|
|
3345
|
-
return () => {
|
|
3346
|
-
slashRegistry.unregister("goal");
|
|
3347
|
-
};
|
|
3348
|
-
}, [slashRegistry]);
|
|
3349
3336
|
useEffect(() => {
|
|
3350
3337
|
if (!getPickableProviders || !switchProviderAndModel) return;
|
|
3351
3338
|
const cmd = {
|
|
@@ -4224,6 +4211,46 @@ function App({
|
|
|
4224
4211
|
};
|
|
4225
4212
|
const runBlocksRef = useRef(runBlocks);
|
|
4226
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]);
|
|
4227
4254
|
const submit = async (overrideRaw) => {
|
|
4228
4255
|
const raw = overrideRaw ?? draftRef.current.buffer;
|
|
4229
4256
|
const trimmed = raw.trim();
|
|
@@ -4258,6 +4285,9 @@ function App({
|
|
|
4258
4285
|
if (getAutonomy) {
|
|
4259
4286
|
const currentAutonomy = getAutonomy();
|
|
4260
4287
|
if (currentAutonomy !== autonomyLive) setAutonomyLive(currentAutonomy);
|
|
4288
|
+
if (currentAutonomy === "eternal" && getEternalEngine) {
|
|
4289
|
+
void runEternalLoopRef.current();
|
|
4290
|
+
}
|
|
4261
4291
|
}
|
|
4262
4292
|
if (res?.exit) {
|
|
4263
4293
|
exit();
|
|
@@ -4450,7 +4480,8 @@ User message:
|
|
|
4450
4480
|
context: contextWindow,
|
|
4451
4481
|
projectName,
|
|
4452
4482
|
subagentCount: Object.keys(state.fleet).length,
|
|
4453
|
-
processCount: getProcessRegistry().activeCount
|
|
4483
|
+
processCount: getProcessRegistry().activeCount,
|
|
4484
|
+
hiddenItems
|
|
4454
4485
|
}
|
|
4455
4486
|
),
|
|
4456
4487
|
director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
|
|
@@ -4578,6 +4609,8 @@ async function runTui(opts) {
|
|
|
4578
4609
|
yolo: opts.yolo,
|
|
4579
4610
|
getYolo: opts.getYolo,
|
|
4580
4611
|
getAutonomy: opts.getAutonomy,
|
|
4612
|
+
getEternalEngine: opts.getEternalEngine,
|
|
4613
|
+
subscribeEternalIteration: opts.subscribeEternalIteration,
|
|
4581
4614
|
appVersion: opts.appVersion,
|
|
4582
4615
|
provider: opts.provider,
|
|
4583
4616
|
family: opts.family,
|
|
@@ -4590,6 +4623,8 @@ async function runTui(opts) {
|
|
|
4590
4623
|
fleetRoster: opts.fleetRoster,
|
|
4591
4624
|
onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory(dispatch) : void 0,
|
|
4592
4625
|
fleetStreamController: opts.fleetStreamController,
|
|
4626
|
+
statuslineHiddenItems: opts.statuslineHiddenItems,
|
|
4627
|
+
setStatuslineHiddenItems: opts.setStatuslineHiddenItems,
|
|
4593
4628
|
initialGoal: opts.initialGoal,
|
|
4594
4629
|
initialAsk: opts.initialAsk,
|
|
4595
4630
|
getSDDContext: opts.getSDDContext,
|
|
@@ -4627,6 +4662,6 @@ async function runTui(opts) {
|
|
|
4627
4662
|
});
|
|
4628
4663
|
}
|
|
4629
4664
|
|
|
4630
|
-
export { runTui };
|
|
4665
|
+
export { buildGoalPreamble, runTui };
|
|
4631
4666
|
//# sourceMappingURL=index.js.map
|
|
4632
4667
|
//# sourceMappingURL=index.js.map
|