@wrongstack/tui 0.4.0 → 0.5.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 +14 -0
- package/dist/index.js +104 -23
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ interface RunTuiOptions {
|
|
|
26
26
|
queueStore?: QueueStore;
|
|
27
27
|
/** Surfaces the "⚠ YOLO" chip in the status bar. */
|
|
28
28
|
yolo?: boolean;
|
|
29
|
+
/** Query live YOLO state from the permission policy. */
|
|
30
|
+
getYolo?: () => boolean;
|
|
31
|
+
/** Query the live autonomy mode. */
|
|
32
|
+
getAutonomy?: () => 'off' | 'suggest' | 'auto';
|
|
29
33
|
/** Renders in the startup banner. Read from the CLI's package.json. */
|
|
30
34
|
appVersion?: string;
|
|
31
35
|
/** Provider id for the startup banner ("openai", "anthropic", ...). */
|
|
@@ -110,6 +114,16 @@ interface RunTuiOptions {
|
|
|
110
114
|
* Ignored when `initialGoal` is also set.
|
|
111
115
|
*/
|
|
112
116
|
initialAsk?: string;
|
|
117
|
+
/**
|
|
118
|
+
* SDD session context getter. When an SDD session is active, returns
|
|
119
|
+
* the AI prompt context to inject into user messages.
|
|
120
|
+
*/
|
|
121
|
+
getSDDContext?: () => string | null;
|
|
122
|
+
/**
|
|
123
|
+
* Process AI output for SDD auto-detection (spec, tasks, plan).
|
|
124
|
+
* Returns displayable status messages.
|
|
125
|
+
*/
|
|
126
|
+
onSDDOutput?: (output: string) => Promise<string[]>;
|
|
113
127
|
}
|
|
114
128
|
declare function runTui(opts: RunTuiOptions): Promise<number>;
|
|
115
129
|
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { render, useApp, Box, useStdout, Static, Text, useInput, useStdin } from 'ink';
|
|
2
|
-
import
|
|
2
|
+
import React2, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
3
3
|
import * as fs2 from 'fs/promises';
|
|
4
4
|
import * as path2 from 'path';
|
|
5
|
-
import { InputBuilder, formatTodosList } from '@wrongstack/core';
|
|
5
|
+
import { InputBuilder, formatTodosList, buildChildEnv } from '@wrongstack/core';
|
|
6
6
|
import { routeImagesForModel } from '@wrongstack/runtime/vision';
|
|
7
7
|
import { readClipboardImage } from '@wrongstack/runtime/clipboard';
|
|
8
8
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
@@ -39,7 +39,7 @@ function ConfirmPrompt({
|
|
|
39
39
|
suggestedPattern,
|
|
40
40
|
onDecision
|
|
41
41
|
}) {
|
|
42
|
-
|
|
42
|
+
React2.useEffect(() => {
|
|
43
43
|
process.stdout.write("\x07");
|
|
44
44
|
}, []);
|
|
45
45
|
useInput((input2, key) => {
|
|
@@ -244,6 +244,15 @@ function FleetPanel({ entries, totalCost, roster }) {
|
|
|
244
244
|
"msg: ",
|
|
245
245
|
message
|
|
246
246
|
] }) }, `${entry.id}-msg-${index}-${message}`)),
|
|
247
|
+
entry.budgetWarning ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
248
|
+
"\u26A1 hitting ",
|
|
249
|
+
entry.budgetWarning.kind,
|
|
250
|
+
" limit (",
|
|
251
|
+
entry.budgetWarning.used,
|
|
252
|
+
"/",
|
|
253
|
+
entry.budgetWarning.limit,
|
|
254
|
+
") \u2014 extending"
|
|
255
|
+
] }) }) : null,
|
|
247
256
|
entry.status === "running" && entry.streamingText ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
248
257
|
">",
|
|
249
258
|
" ",
|
|
@@ -1563,6 +1572,7 @@ function StatusBar({
|
|
|
1563
1572
|
hint,
|
|
1564
1573
|
queueCount = 0,
|
|
1565
1574
|
yolo = false,
|
|
1575
|
+
autonomy,
|
|
1566
1576
|
elapsedMs,
|
|
1567
1577
|
todos,
|
|
1568
1578
|
plan,
|
|
@@ -1578,7 +1588,7 @@ function StatusBar({
|
|
|
1578
1588
|
const cache2 = tokenCounter?.cacheStats();
|
|
1579
1589
|
const stateColor = state === "idle" ? "cyan" : state === "aborting" ? "yellow" : "green";
|
|
1580
1590
|
const stateLabel = state === "idle" ? "idle" : state === "aborting" ? "aborting\u2026" : "thinking\u2026";
|
|
1581
|
-
const hasSecondLine = yolo || elapsedMs !== void 0 || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0;
|
|
1591
|
+
const hasSecondLine = yolo || autonomy && autonomy !== "off" || elapsedMs !== void 0 || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0;
|
|
1582
1592
|
const fleetHasActivity = fleet && (fleet.running > 0 || fleet.idle > 0 || fleet.pending > 0 || fleet.completed > 0) || subagentCount > 0;
|
|
1583
1593
|
const hasThirdLine = todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) || fleetHasActivity;
|
|
1584
1594
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1643,6 +1653,13 @@ function StatusBar({
|
|
|
1643
1653
|
] }),
|
|
1644
1654
|
hasSecondLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
|
|
1645
1655
|
yolo ? /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "\u26A0 YOLO" }) : null,
|
|
1656
|
+
autonomy && autonomy !== "off" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1657
|
+
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1658
|
+
/* @__PURE__ */ jsxs(Text, { color: autonomy === "auto" ? "yellow" : "cyan", bold: true, children: [
|
|
1659
|
+
"\u221E ",
|
|
1660
|
+
autonomy.toUpperCase()
|
|
1661
|
+
] })
|
|
1662
|
+
] }) : null,
|
|
1646
1663
|
elapsedMs !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1647
1664
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1648
1665
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
@@ -1927,6 +1944,7 @@ function runGit(cwd, args) {
|
|
|
1927
1944
|
try {
|
|
1928
1945
|
const child = spawn("git", args, {
|
|
1929
1946
|
cwd,
|
|
1947
|
+
env: buildChildEnv(),
|
|
1930
1948
|
// Inherit stderr (silent) — we don't care about git's noise.
|
|
1931
1949
|
stdio: ["ignore", "pipe", "ignore"],
|
|
1932
1950
|
// Don't let a slow git hang the TUI.
|
|
@@ -2340,6 +2358,8 @@ function reducer(state, action) {
|
|
|
2340
2358
|
...cur,
|
|
2341
2359
|
status: "running",
|
|
2342
2360
|
streamingText: "",
|
|
2361
|
+
budgetWarning: void 0,
|
|
2362
|
+
// clear on restart
|
|
2343
2363
|
startedAt: Date.now()
|
|
2344
2364
|
}
|
|
2345
2365
|
}
|
|
@@ -2422,6 +2442,23 @@ function reducer(state, action) {
|
|
|
2422
2442
|
toolCalls: action.toolCalls,
|
|
2423
2443
|
streamingText: "",
|
|
2424
2444
|
currentTool: void 0,
|
|
2445
|
+
budgetWarning: void 0,
|
|
2446
|
+
// clear on done/restart
|
|
2447
|
+
lastEventAt: Date.now()
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
case "fleetBudgetWarning": {
|
|
2453
|
+
const cur = state.fleet[action.id];
|
|
2454
|
+
if (!cur) return state;
|
|
2455
|
+
return {
|
|
2456
|
+
...state,
|
|
2457
|
+
fleet: {
|
|
2458
|
+
...state.fleet,
|
|
2459
|
+
[action.id]: {
|
|
2460
|
+
...cur,
|
|
2461
|
+
budgetWarning: { kind: action.kind, used: action.used, limit: action.limit, at: Date.now() },
|
|
2425
2462
|
lastEventAt: Date.now()
|
|
2426
2463
|
}
|
|
2427
2464
|
}
|
|
@@ -2551,6 +2588,10 @@ function App({
|
|
|
2551
2588
|
banner = true,
|
|
2552
2589
|
queueStore,
|
|
2553
2590
|
yolo = false,
|
|
2591
|
+
getYolo,
|
|
2592
|
+
getAutonomy,
|
|
2593
|
+
getSDDContext,
|
|
2594
|
+
onSDDOutput,
|
|
2554
2595
|
appVersion,
|
|
2555
2596
|
provider,
|
|
2556
2597
|
family,
|
|
@@ -2569,6 +2610,8 @@ function App({
|
|
|
2569
2610
|
const { exit } = useApp();
|
|
2570
2611
|
const [liveModel, setLiveModel] = useState(model);
|
|
2571
2612
|
const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
|
|
2613
|
+
const [yoloLive, setYoloLive] = useState(yolo);
|
|
2614
|
+
const [autonomyLive, setAutonomyLive] = useState(getAutonomy?.() ?? "off");
|
|
2572
2615
|
const [state, dispatch] = useReducer(reducer, {
|
|
2573
2616
|
entries: banner ? [
|
|
2574
2617
|
{
|
|
@@ -2621,7 +2664,7 @@ function App({
|
|
|
2621
2664
|
const inputGateRef = useRef(false);
|
|
2622
2665
|
const lastEnterAtRef = useRef(0);
|
|
2623
2666
|
const projectRoot = agent.ctx.projectRoot;
|
|
2624
|
-
const projectName =
|
|
2667
|
+
const projectName = React2.useMemo(() => {
|
|
2625
2668
|
const base = path2.basename(projectRoot);
|
|
2626
2669
|
return base && base !== path2.sep ? base : void 0;
|
|
2627
2670
|
}, [projectRoot]);
|
|
@@ -2641,13 +2684,13 @@ function App({
|
|
|
2641
2684
|
dispatch({ type: "clearInput" });
|
|
2642
2685
|
};
|
|
2643
2686
|
const startedAtRef = useRef(Date.now());
|
|
2644
|
-
const [nowTick, setNowTick] =
|
|
2687
|
+
const [nowTick, setNowTick] = React2.useState(Date.now());
|
|
2645
2688
|
useEffect(() => {
|
|
2646
2689
|
const t = setInterval(() => setNowTick(Date.now()), 1e3);
|
|
2647
2690
|
return () => clearInterval(t);
|
|
2648
2691
|
}, []);
|
|
2649
2692
|
const elapsedMs = nowTick - startedAtRef.current;
|
|
2650
|
-
const [gitInfo, setGitInfo] =
|
|
2693
|
+
const [gitInfo, setGitInfo] = React2.useState(null);
|
|
2651
2694
|
useEffect(() => {
|
|
2652
2695
|
let cancelled = false;
|
|
2653
2696
|
const refresh = () => {
|
|
@@ -2662,21 +2705,13 @@ function App({
|
|
|
2662
2705
|
clearInterval(t);
|
|
2663
2706
|
};
|
|
2664
2707
|
}, [agent.ctx.cwd]);
|
|
2665
|
-
|
|
2666
|
-
useEffect(() => {
|
|
2667
|
-
const off = events.on("provider.response", (e) => {
|
|
2668
|
-
const total = (e.usage.input ?? 0) + (e.usage.cacheRead ?? 0) + (e.usage.cacheWrite ?? 0);
|
|
2669
|
-
setLastInputTokens(total);
|
|
2670
|
-
});
|
|
2671
|
-
return () => {
|
|
2672
|
-
off();
|
|
2673
|
-
};
|
|
2674
|
-
}, [events]);
|
|
2708
|
+
(tokenCounter?.total().input ?? 0) + (tokenCounter?.total().cacheRead ?? 0) + (tokenCounter?.total().cacheWrite ?? 0);
|
|
2675
2709
|
const maxContext = effectiveMaxContext ?? agent.ctx.provider.capabilities.maxContext;
|
|
2710
|
+
const currentContextTokens = (tokenCounter?.currentRequestTokens()?.input ?? 0) + (tokenCounter?.currentRequestTokens()?.cacheRead ?? 0);
|
|
2676
2711
|
const contextWindow = useMemo(() => {
|
|
2677
2712
|
void state.contextChipVersion;
|
|
2678
|
-
return
|
|
2679
|
-
}, [
|
|
2713
|
+
return currentContextTokens > 0 && maxContext > 0 ? { used: currentContextTokens, max: maxContext } : void 0;
|
|
2714
|
+
}, [currentContextTokens, maxContext, state.contextChipVersion]);
|
|
2680
2715
|
const todos = useMemo(() => {
|
|
2681
2716
|
const counts = { pending: 0, inProgress: 0, completed: 0 };
|
|
2682
2717
|
for (const t of agent.ctx.todos) {
|
|
@@ -3272,6 +3307,20 @@ function App({
|
|
|
3272
3307
|
}
|
|
3273
3308
|
});
|
|
3274
3309
|
});
|
|
3310
|
+
const offBudgetWarning = events.on("subagent.budget_warning", (e) => {
|
|
3311
|
+
const lbl = labelFor(e.subagentId);
|
|
3312
|
+
dispatch({ type: "fleetBudgetWarning", id: e.subagentId, kind: e.kind, used: e.used, limit: e.limit });
|
|
3313
|
+
dispatch({
|
|
3314
|
+
type: "addEntry",
|
|
3315
|
+
entry: {
|
|
3316
|
+
kind: "subagent",
|
|
3317
|
+
agentLabel: lbl.label,
|
|
3318
|
+
agentColor: lbl.color,
|
|
3319
|
+
icon: "\u26A1",
|
|
3320
|
+
text: `hitting ${e.kind} limit (${e.used}/${e.limit}) \u2014 extending`
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
3323
|
+
});
|
|
3275
3324
|
const offTool = events.on("subagent.tool_executed", (e) => {
|
|
3276
3325
|
if (director) return;
|
|
3277
3326
|
dispatch({
|
|
@@ -3288,6 +3337,7 @@ function App({
|
|
|
3288
3337
|
offSpawned();
|
|
3289
3338
|
offStarted();
|
|
3290
3339
|
offCompleted();
|
|
3340
|
+
offBudgetWarning();
|
|
3291
3341
|
offTool();
|
|
3292
3342
|
};
|
|
3293
3343
|
}, [events, director]);
|
|
@@ -3521,7 +3571,7 @@ function App({
|
|
|
3521
3571
|
return () => {
|
|
3522
3572
|
process.off("SIGINT", onSigint);
|
|
3523
3573
|
};
|
|
3524
|
-
}, [
|
|
3574
|
+
}, [director]);
|
|
3525
3575
|
const handleKey = async (input, key) => {
|
|
3526
3576
|
if (state.status === "aborting" && state.interrupts === 0) return;
|
|
3527
3577
|
if (state.confirmQueue.length > 0) return;
|
|
@@ -3848,6 +3898,15 @@ function App({
|
|
|
3848
3898
|
entry: { kind: "warn", text: `Hit max iterations (${result.iterations}).` }
|
|
3849
3899
|
});
|
|
3850
3900
|
}
|
|
3901
|
+
if (result.status === "done" && result.finalText && onSDDOutput) {
|
|
3902
|
+
try {
|
|
3903
|
+
const sddMessages = await onSDDOutput(result.finalText);
|
|
3904
|
+
for (const msg of sddMessages) {
|
|
3905
|
+
dispatch({ type: "addEntry", entry: { kind: "info", text: msg } });
|
|
3906
|
+
}
|
|
3907
|
+
} catch {
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3851
3910
|
if (tokenCounter && before) {
|
|
3852
3911
|
const after = tokenCounter.total();
|
|
3853
3912
|
const costAfter = tokenCounter.estimateCost().total;
|
|
@@ -3903,6 +3962,14 @@ function App({
|
|
|
3903
3962
|
if (ctxModel && ctxModel !== liveModel) setLiveModel(ctxModel);
|
|
3904
3963
|
const ctxProviderId = agent.ctx.provider?.id;
|
|
3905
3964
|
if (ctxProviderId && ctxProviderId !== liveProvider) setLiveProvider(ctxProviderId);
|
|
3965
|
+
if (getYolo) {
|
|
3966
|
+
const currentYolo = getYolo();
|
|
3967
|
+
if (currentYolo !== yoloLive) setYoloLive(currentYolo);
|
|
3968
|
+
}
|
|
3969
|
+
if (getAutonomy) {
|
|
3970
|
+
const currentAutonomy = getAutonomy();
|
|
3971
|
+
if (currentAutonomy !== autonomyLive) setAutonomyLive(currentAutonomy);
|
|
3972
|
+
}
|
|
3906
3973
|
if (res?.exit) {
|
|
3907
3974
|
exit();
|
|
3908
3975
|
onExit(0);
|
|
@@ -3934,6 +4001,15 @@ function App({
|
|
|
3934
4001
|
const builder = builderRef.current;
|
|
3935
4002
|
if (!builder) return;
|
|
3936
4003
|
const steering = state.steeringPending;
|
|
4004
|
+
const sddContext = getSDDContext?.();
|
|
4005
|
+
if (sddContext && trimmed) {
|
|
4006
|
+
builder.appendText(`[SDD SESSION ACTIVE]
|
|
4007
|
+
${sddContext}
|
|
4008
|
+
|
|
4009
|
+
---
|
|
4010
|
+
User message:
|
|
4011
|
+
`);
|
|
4012
|
+
}
|
|
3937
4013
|
if (trimmed) {
|
|
3938
4014
|
const toAppend = steering ? buildSteeringPreamble(state.steerSnapshot, trimmed) : trimmed;
|
|
3939
4015
|
builder.appendText(toAppend);
|
|
@@ -4064,7 +4140,8 @@ function App({
|
|
|
4064
4140
|
tokenCounter,
|
|
4065
4141
|
hint: renderRunningTools(state.runningTools) || state.hint,
|
|
4066
4142
|
queueCount: state.queue.length,
|
|
4067
|
-
yolo,
|
|
4143
|
+
yolo: yoloLive,
|
|
4144
|
+
autonomy: autonomyLive,
|
|
4068
4145
|
elapsedMs,
|
|
4069
4146
|
todos,
|
|
4070
4147
|
plan: planCounts ?? void 0,
|
|
@@ -4187,7 +4264,7 @@ async function runTui(opts) {
|
|
|
4187
4264
|
let instance;
|
|
4188
4265
|
try {
|
|
4189
4266
|
instance = render(
|
|
4190
|
-
|
|
4267
|
+
React2.createElement(App, {
|
|
4191
4268
|
agent: opts.agent,
|
|
4192
4269
|
slashRegistry: opts.slashRegistry,
|
|
4193
4270
|
attachments: opts.attachments,
|
|
@@ -4199,6 +4276,8 @@ async function runTui(opts) {
|
|
|
4199
4276
|
banner: opts.banner ?? true,
|
|
4200
4277
|
queueStore: opts.queueStore,
|
|
4201
4278
|
yolo: opts.yolo,
|
|
4279
|
+
getYolo: opts.getYolo,
|
|
4280
|
+
getAutonomy: opts.getAutonomy,
|
|
4202
4281
|
appVersion: opts.appVersion,
|
|
4203
4282
|
provider: opts.provider,
|
|
4204
4283
|
family: opts.family,
|
|
@@ -4212,7 +4291,9 @@ async function runTui(opts) {
|
|
|
4212
4291
|
onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory(dispatch) : void 0,
|
|
4213
4292
|
fleetStreamController: opts.fleetStreamController,
|
|
4214
4293
|
initialGoal: opts.initialGoal,
|
|
4215
|
-
initialAsk: opts.initialAsk
|
|
4294
|
+
initialAsk: opts.initialAsk,
|
|
4295
|
+
getSDDContext: opts.getSDDContext,
|
|
4296
|
+
onSDDOutput: opts.onSDDOutput
|
|
4216
4297
|
}),
|
|
4217
4298
|
{ exitOnCtrlC: false }
|
|
4218
4299
|
);
|