@wrongstack/tui 0.5.7 → 0.6.1
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 +29 -1
- package/dist/index.js +78 -111
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import * as _wrongstack_core from '@wrongstack/core';
|
|
1
2
|
import { Agent, SlashCommandRegistry, AttachmentStore, EventBus, TokenCounter, QueueStore, Director } from '@wrongstack/core';
|
|
3
|
+
export { buildGoalPreamble } from '@wrongstack/core';
|
|
2
4
|
import { VisionAdapters } from '@wrongstack/runtime/vision';
|
|
3
5
|
import React from 'react';
|
|
4
6
|
|
|
@@ -29,7 +31,19 @@ interface RunTuiOptions {
|
|
|
29
31
|
/** Query live YOLO state from the permission policy. */
|
|
30
32
|
getYolo?: () => boolean;
|
|
31
33
|
/** Query the live autonomy mode. */
|
|
32
|
-
getAutonomy?: () => 'off' | 'suggest' | 'auto';
|
|
34
|
+
getAutonomy?: () => 'off' | 'suggest' | 'auto' | 'eternal';
|
|
35
|
+
/**
|
|
36
|
+
* Access the eternal-autonomy engine. When autonomy mode flips to
|
|
37
|
+
* 'eternal' the TUI drives `runOneIteration()` from the post-slash hook
|
|
38
|
+
* so the engine and TUI never race for the shared Context.
|
|
39
|
+
*/
|
|
40
|
+
getEternalEngine?: () => _wrongstack_core.EternalAutonomyEngine | null;
|
|
41
|
+
/**
|
|
42
|
+
* Subscribe to live per-iteration events from the eternal engine.
|
|
43
|
+
* Returns an unsubscribe function. TUI uses this to render each
|
|
44
|
+
* iteration as a live timeline entry as it lands.
|
|
45
|
+
*/
|
|
46
|
+
subscribeEternalIteration?: (fn: (entry: _wrongstack_core.JournalEntry) => void) => () => void;
|
|
33
47
|
/** Renders in the startup banner. Read from the CLI's package.json. */
|
|
34
48
|
appVersion?: string;
|
|
35
49
|
/** Provider id for the startup banner ("openai", "anthropic", ...). */
|
|
@@ -88,6 +102,12 @@ interface RunTuiOptions {
|
|
|
88
102
|
fleetRoster?: Record<string, {
|
|
89
103
|
name: string;
|
|
90
104
|
}>;
|
|
105
|
+
/**
|
|
106
|
+
* Shared controller for the `/fleet stream on|off` toggle. The slash
|
|
107
|
+
* command runs in the CLI process and needs to flip TUI reducer state;
|
|
108
|
+
* the App installs a dispatch-backed `setEnabled` here on mount so
|
|
109
|
+
* both sides stay synchronized.
|
|
110
|
+
*/
|
|
91
111
|
/**
|
|
92
112
|
* Shared controller for the `/fleet stream on|off` toggle. The slash
|
|
93
113
|
* command runs in the CLI process and needs to flip TUI reducer state;
|
|
@@ -98,6 +118,14 @@ interface RunTuiOptions {
|
|
|
98
118
|
enabled: boolean;
|
|
99
119
|
setEnabled: (enabled: boolean) => void;
|
|
100
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* Controller for status bar hidden items. App installs a dispatch-backed
|
|
123
|
+
* setter on mount so the /statusline slash command can update the TUI's
|
|
124
|
+
* visible bar without a round-trip. The initial value is loaded from
|
|
125
|
+
* the config file before App mounts.
|
|
126
|
+
*/
|
|
127
|
+
statuslineHiddenItems: Array<'todos' | 'plan' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>;
|
|
128
|
+
setStatuslineHiddenItems: (items: Array<'todos' | 'plan' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>) => void;
|
|
101
129
|
/**
|
|
102
130
|
* If set, the App boots straight into goal mode — the text is wrapped
|
|
103
131
|
* in the GOAL preamble and submitted as the first turn. Lets users
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
-
import { InputBuilder, DefaultSessionRewinder, formatTodosList, buildChildEnv } from '@wrongstack/core';
|
|
5
|
+
import { InputBuilder, DefaultSessionRewinder, formatTodosList, buildGoalPreamble, buildChildEnv } from '@wrongstack/core';
|
|
6
|
+
export { buildGoalPreamble } from '@wrongstack/core';
|
|
6
7
|
import { routeImagesForModel } from '@wrongstack/runtime/vision';
|
|
7
8
|
import { getProcessRegistry } from '@wrongstack/tools';
|
|
8
9
|
import { readClipboardImage } from '@wrongstack/runtime/clipboard';
|
|
@@ -1632,8 +1633,10 @@ function StatusBar({
|
|
|
1632
1633
|
subagentCount = 0,
|
|
1633
1634
|
context,
|
|
1634
1635
|
projectName,
|
|
1635
|
-
processCount
|
|
1636
|
+
processCount,
|
|
1637
|
+
hiddenItems
|
|
1636
1638
|
}) {
|
|
1639
|
+
const hiddenSet = new Set(hiddenItems);
|
|
1637
1640
|
const usage = tokenCounter?.total();
|
|
1638
1641
|
const cost = tokenCounter?.estimateCost();
|
|
1639
1642
|
const cache2 = tokenCounter?.cacheStats();
|
|
@@ -1715,12 +1718,19 @@ function StatusBar({
|
|
|
1715
1718
|
yolo ? /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "\u26A0 YOLO" }) : null,
|
|
1716
1719
|
autonomy && autonomy !== "off" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1717
1720
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1718
|
-
/* @__PURE__ */ jsxs(
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1721
|
+
/* @__PURE__ */ jsxs(
|
|
1722
|
+
Text,
|
|
1723
|
+
{
|
|
1724
|
+
color: autonomy === "eternal" ? "red" : autonomy === "auto" ? "yellow" : "cyan",
|
|
1725
|
+
bold: true,
|
|
1726
|
+
children: [
|
|
1727
|
+
"\u221E ",
|
|
1728
|
+
autonomy.toUpperCase()
|
|
1729
|
+
]
|
|
1730
|
+
}
|
|
1731
|
+
)
|
|
1722
1732
|
] }) : null,
|
|
1723
|
-
elapsedMs !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1733
|
+
elapsedMs !== void 0 && !hiddenSet.has("elapsed") ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1724
1734
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1725
1735
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1726
1736
|
"\u23F1 ",
|
|
@@ -2693,75 +2703,6 @@ function buildSteeringPreamble(snapshot, newDirection) {
|
|
|
2693
2703
|
lines.push("]");
|
|
2694
2704
|
return lines.join("\n");
|
|
2695
2705
|
}
|
|
2696
|
-
function buildGoalPreamble(goal) {
|
|
2697
|
-
return [
|
|
2698
|
-
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
2699
|
-
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
2700
|
-
"",
|
|
2701
|
-
"YOUR GOAL:",
|
|
2702
|
-
"---",
|
|
2703
|
-
goal,
|
|
2704
|
-
"---",
|
|
2705
|
-
"",
|
|
2706
|
-
"AUTHORITY YOU HAVE:",
|
|
2707
|
-
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
2708
|
-
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
2709
|
-
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
2710
|
-
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
2711
|
-
" batch work, specialist model for domain code.",
|
|
2712
|
-
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
2713
|
-
" The Agent loop auto-extends every 100 iterations forever.",
|
|
2714
|
-
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
2715
|
-
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
2716
|
-
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
2717
|
-
" to stick with the first plan you proposed.",
|
|
2718
|
-
"",
|
|
2719
|
-
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
2720
|
-
"- You can name a concrete artifact (a passing test, a written file at",
|
|
2721
|
-
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
2722
|
-
" a clean grep that previously had matches).",
|
|
2723
|
-
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
2724
|
-
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
2725
|
-
' believe this fixes it", "the changes appear correct".',
|
|
2726
|
-
"",
|
|
2727
|
-
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
2728
|
-
"- An error message you didn't recover from.",
|
|
2729
|
-
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
2730
|
-
" without questioning the search.",
|
|
2731
|
-
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
2732
|
-
' want X." Those are hedges. The user already told you to finish the',
|
|
2733
|
-
" goal \u2014 just do it.",
|
|
2734
|
-
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
2735
|
-
" done, not done.",
|
|
2736
|
-
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
2737
|
-
" respond to with a fresh attempt (different role, different model,",
|
|
2738
|
-
" tighter prompt).",
|
|
2739
|
-
"",
|
|
2740
|
-
"PERSISTENCE PROTOCOL:",
|
|
2741
|
-
"- If blocked, try at least 3 different angles before reporting the",
|
|
2742
|
-
" problem to the user. Different tool inputs, different subagent",
|
|
2743
|
-
" roles, different providers, different decomposition of the task.",
|
|
2744
|
-
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
2745
|
-
" not just report the failure back.",
|
|
2746
|
-
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
2747
|
-
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
2748
|
-
" final answer.",
|
|
2749
|
-
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
2750
|
-
" full delegated task.",
|
|
2751
|
-
"",
|
|
2752
|
-
"REPORTING:",
|
|
2753
|
-
"- Stream short progress notes between major actions so the user can",
|
|
2754
|
-
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
2755
|
-
" text \u2014 but also do not narrate every tool call.",
|
|
2756
|
-
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
2757
|
-
" subagents can read.",
|
|
2758
|
-
"- Final response must include: (a) what was accomplished, (b) how",
|
|
2759
|
-
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
2760
|
-
" should know about).",
|
|
2761
|
-
"",
|
|
2762
|
-
"BEGIN.]"
|
|
2763
|
-
].join("\n");
|
|
2764
|
-
}
|
|
2765
2706
|
function App({
|
|
2766
2707
|
agent,
|
|
2767
2708
|
slashRegistry,
|
|
@@ -2776,6 +2717,8 @@ function App({
|
|
|
2776
2717
|
yolo = false,
|
|
2777
2718
|
getYolo,
|
|
2778
2719
|
getAutonomy,
|
|
2720
|
+
getEternalEngine,
|
|
2721
|
+
subscribeEternalIteration,
|
|
2779
2722
|
getSDDContext,
|
|
2780
2723
|
onSDDOutput,
|
|
2781
2724
|
appVersion,
|
|
@@ -2790,6 +2733,8 @@ function App({
|
|
|
2790
2733
|
fleetRoster,
|
|
2791
2734
|
onClearHistory,
|
|
2792
2735
|
fleetStreamController,
|
|
2736
|
+
statuslineHiddenItems,
|
|
2737
|
+
setStatuslineHiddenItems,
|
|
2793
2738
|
initialGoal,
|
|
2794
2739
|
initialAsk,
|
|
2795
2740
|
sessionsDir
|
|
@@ -2799,6 +2744,13 @@ function App({
|
|
|
2799
2744
|
const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
|
|
2800
2745
|
const [yoloLive, setYoloLive] = useState(yolo);
|
|
2801
2746
|
const [autonomyLive, setAutonomyLive] = useState(getAutonomy?.() ?? "off");
|
|
2747
|
+
const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
|
|
2748
|
+
useEffect(() => {
|
|
2749
|
+
setHiddenItems(statuslineHiddenItems);
|
|
2750
|
+
}, [statuslineHiddenItems]);
|
|
2751
|
+
useEffect(() => {
|
|
2752
|
+
setStatuslineHiddenItems(hiddenItems);
|
|
2753
|
+
}, [setStatuslineHiddenItems]);
|
|
2802
2754
|
const [state, dispatch] = useReducer(reducer, {
|
|
2803
2755
|
entries: banner ? [
|
|
2804
2756
|
{
|
|
@@ -3313,39 +3265,6 @@ function App({
|
|
|
3313
3265
|
slashRegistry.unregister("rewind");
|
|
3314
3266
|
};
|
|
3315
3267
|
}, [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
3268
|
useEffect(() => {
|
|
3350
3269
|
if (!getPickableProviders || !switchProviderAndModel) return;
|
|
3351
3270
|
const cmd = {
|
|
@@ -4224,6 +4143,46 @@ function App({
|
|
|
4224
4143
|
};
|
|
4225
4144
|
const runBlocksRef = useRef(runBlocks);
|
|
4226
4145
|
runBlocksRef.current = runBlocks;
|
|
4146
|
+
const runEternalLoop = async () => {
|
|
4147
|
+
const engine = getEternalEngine?.();
|
|
4148
|
+
if (!engine) return;
|
|
4149
|
+
if (eternalLoopRunningRef.current) return;
|
|
4150
|
+
eternalLoopRunningRef.current = true;
|
|
4151
|
+
try {
|
|
4152
|
+
while (true) {
|
|
4153
|
+
const liveMode = getAutonomy?.() ?? "off";
|
|
4154
|
+
if (liveMode !== "eternal") break;
|
|
4155
|
+
if (engine.currentState === "stopped") break;
|
|
4156
|
+
dispatch({ type: "status", status: "running" });
|
|
4157
|
+
try {
|
|
4158
|
+
await engine.runOneIteration();
|
|
4159
|
+
} catch (err) {
|
|
4160
|
+
dispatch({
|
|
4161
|
+
type: "addEntry",
|
|
4162
|
+
entry: { kind: "error", text: `[eternal] ${err instanceof Error ? err.message : String(err)}` }
|
|
4163
|
+
});
|
|
4164
|
+
}
|
|
4165
|
+
dispatch({ type: "status", status: "idle" });
|
|
4166
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
4167
|
+
}
|
|
4168
|
+
} finally {
|
|
4169
|
+
eternalLoopRunningRef.current = false;
|
|
4170
|
+
}
|
|
4171
|
+
};
|
|
4172
|
+
const eternalLoopRunningRef = useRef(false);
|
|
4173
|
+
const runEternalLoopRef = useRef(runEternalLoop);
|
|
4174
|
+
runEternalLoopRef.current = runEternalLoop;
|
|
4175
|
+
useEffect(() => {
|
|
4176
|
+
if (!subscribeEternalIteration) return;
|
|
4177
|
+
const unsub = subscribeEternalIteration((entry) => {
|
|
4178
|
+
const mark = entry.status === "success" ? "\u2713" : entry.status === "failure" ? "\u2717" : entry.status === "aborted" ? "\u2298" : "\xB7";
|
|
4179
|
+
const cost = typeof entry.costUsd === "number" ? ` ($${entry.costUsd.toFixed(4)})` : "";
|
|
4180
|
+
const note = entry.note ? ` \u2014 ${entry.note.slice(0, 80)}` : "";
|
|
4181
|
+
const text = `#${entry.iteration} ${mark} [${entry.source}] ${entry.task}${cost}${note}`;
|
|
4182
|
+
dispatch({ type: "addEntry", entry: { kind: "info", text } });
|
|
4183
|
+
});
|
|
4184
|
+
return unsub;
|
|
4185
|
+
}, [subscribeEternalIteration]);
|
|
4227
4186
|
const submit = async (overrideRaw) => {
|
|
4228
4187
|
const raw = overrideRaw ?? draftRef.current.buffer;
|
|
4229
4188
|
const trimmed = raw.trim();
|
|
@@ -4258,6 +4217,9 @@ function App({
|
|
|
4258
4217
|
if (getAutonomy) {
|
|
4259
4218
|
const currentAutonomy = getAutonomy();
|
|
4260
4219
|
if (currentAutonomy !== autonomyLive) setAutonomyLive(currentAutonomy);
|
|
4220
|
+
if (currentAutonomy === "eternal" && getEternalEngine) {
|
|
4221
|
+
void runEternalLoopRef.current();
|
|
4222
|
+
}
|
|
4261
4223
|
}
|
|
4262
4224
|
if (res?.exit) {
|
|
4263
4225
|
exit();
|
|
@@ -4450,7 +4412,8 @@ User message:
|
|
|
4450
4412
|
context: contextWindow,
|
|
4451
4413
|
projectName,
|
|
4452
4414
|
subagentCount: Object.keys(state.fleet).length,
|
|
4453
|
-
processCount: getProcessRegistry().activeCount
|
|
4415
|
+
processCount: getProcessRegistry().activeCount,
|
|
4416
|
+
hiddenItems
|
|
4454
4417
|
}
|
|
4455
4418
|
),
|
|
4456
4419
|
director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
|
|
@@ -4578,6 +4541,8 @@ async function runTui(opts) {
|
|
|
4578
4541
|
yolo: opts.yolo,
|
|
4579
4542
|
getYolo: opts.getYolo,
|
|
4580
4543
|
getAutonomy: opts.getAutonomy,
|
|
4544
|
+
getEternalEngine: opts.getEternalEngine,
|
|
4545
|
+
subscribeEternalIteration: opts.subscribeEternalIteration,
|
|
4581
4546
|
appVersion: opts.appVersion,
|
|
4582
4547
|
provider: opts.provider,
|
|
4583
4548
|
family: opts.family,
|
|
@@ -4590,6 +4555,8 @@ async function runTui(opts) {
|
|
|
4590
4555
|
fleetRoster: opts.fleetRoster,
|
|
4591
4556
|
onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory(dispatch) : void 0,
|
|
4592
4557
|
fleetStreamController: opts.fleetStreamController,
|
|
4558
|
+
statuslineHiddenItems: opts.statuslineHiddenItems,
|
|
4559
|
+
setStatuslineHiddenItems: opts.setStatuslineHiddenItems,
|
|
4593
4560
|
initialGoal: opts.initialGoal,
|
|
4594
4561
|
initialAsk: opts.initialAsk,
|
|
4595
4562
|
getSDDContext: opts.getSDDContext,
|