@wrongstack/tui 0.3.4 → 0.3.8
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 +76 -95
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render, useApp, Box, useStdout, Static, Text, useInput, useStdin } from 'ink';
|
|
2
|
-
import
|
|
2
|
+
import React4, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
3
3
|
import * as fs2 from 'fs/promises';
|
|
4
4
|
import * as path2 from 'path';
|
|
5
5
|
import { InputBuilder, formatTodosList } from '@wrongstack/core';
|
|
@@ -39,14 +39,19 @@ function ConfirmPrompt({
|
|
|
39
39
|
suggestedPattern,
|
|
40
40
|
onDecision
|
|
41
41
|
}) {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
React4.useEffect(() => {
|
|
43
|
+
process.stdout.write("\x07");
|
|
44
|
+
}, []);
|
|
45
|
+
useInput((input2, key) => {
|
|
46
|
+
if (!input2 || input2 === "\r" || input2 === "\n") return;
|
|
47
|
+
const ch = input2.toLowerCase();
|
|
48
|
+
if (ch === "y") {
|
|
44
49
|
onDecision("yes");
|
|
45
|
-
} else if (
|
|
50
|
+
} else if (ch === "n") {
|
|
46
51
|
onDecision("no");
|
|
47
|
-
} else if (
|
|
52
|
+
} else if (ch === "a") {
|
|
48
53
|
onDecision("always");
|
|
49
|
-
} else if (
|
|
54
|
+
} else if (ch === "d") {
|
|
50
55
|
onDecision("deny");
|
|
51
56
|
}
|
|
52
57
|
});
|
|
@@ -58,34 +63,32 @@ function ConfirmPrompt({
|
|
|
58
63
|
Box,
|
|
59
64
|
{
|
|
60
65
|
flexDirection: "column",
|
|
61
|
-
borderStyle: "
|
|
62
|
-
|
|
63
|
-
borderLeft: false,
|
|
64
|
-
borderRight: false,
|
|
65
|
-
borderBottom: false,
|
|
66
|
+
borderStyle: "round",
|
|
67
|
+
borderColor: "yellow",
|
|
66
68
|
paddingX: 1,
|
|
69
|
+
marginY: 1,
|
|
67
70
|
children: [
|
|
68
71
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
69
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0
|
|
72
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 APPROVAL REQUIRED" }),
|
|
70
73
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
71
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: toolName })
|
|
74
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: toolName })
|
|
72
75
|
] }),
|
|
73
76
|
inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
|
|
74
77
|
showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff) }) : null,
|
|
75
78
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
76
79
|
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
77
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[
|
|
78
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
79
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[
|
|
80
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
81
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[
|
|
80
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[y]" }),
|
|
81
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "es " }),
|
|
82
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[n]" }),
|
|
83
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "o " }),
|
|
84
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[a]" }),
|
|
82
85
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
83
|
-
"
|
|
86
|
+
"lways (",
|
|
84
87
|
suggestedPattern,
|
|
85
88
|
") "
|
|
86
89
|
] }),
|
|
87
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[
|
|
88
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
90
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[d]" }),
|
|
91
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "eny" })
|
|
89
92
|
] }) })
|
|
90
93
|
]
|
|
91
94
|
}
|
|
@@ -611,41 +614,13 @@ function Entry({
|
|
|
611
614
|
case "turn-summary":
|
|
612
615
|
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
|
|
613
616
|
case "confirm":
|
|
614
|
-
return /* @__PURE__ */ jsxs(
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
borderRight: false,
|
|
622
|
-
borderBottom: false,
|
|
623
|
-
paddingX: 1,
|
|
624
|
-
children: [
|
|
625
|
-
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
626
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
627
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
628
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: entry.toolName })
|
|
629
|
-
] }),
|
|
630
|
-
entry.input && typeof entry.input === "object" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: Object.entries(entry.input).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${String(v).slice(0, 80)}`).join(" ") }) : null,
|
|
631
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
632
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
633
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
634
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
635
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
636
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
637
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
638
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
639
|
-
" always (",
|
|
640
|
-
entry.suggestedPattern,
|
|
641
|
-
") "
|
|
642
|
-
] }),
|
|
643
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
644
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
645
|
-
] }) })
|
|
646
|
-
]
|
|
647
|
-
}
|
|
648
|
-
);
|
|
617
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1, children: [
|
|
618
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", children: [
|
|
619
|
+
"\u26A0 Confirm: ",
|
|
620
|
+
entry.toolName
|
|
621
|
+
] }),
|
|
622
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Waiting for y / n / a / d..." })
|
|
623
|
+
] });
|
|
649
624
|
case "banner":
|
|
650
625
|
return /* @__PURE__ */ jsx(Banner, { entry });
|
|
651
626
|
case "subagent": {
|
|
@@ -2291,9 +2266,9 @@ function reducer(state, action) {
|
|
|
2291
2266
|
modelPicker: { ...state.modelPicker, hint: action.text }
|
|
2292
2267
|
};
|
|
2293
2268
|
case "confirmOpen":
|
|
2294
|
-
return { ...state,
|
|
2269
|
+
return { ...state, confirmQueue: [...state.confirmQueue, action.info] };
|
|
2295
2270
|
case "confirmClose":
|
|
2296
|
-
return { ...state,
|
|
2271
|
+
return { ...state, confirmQueue: state.confirmQueue.slice(1) };
|
|
2297
2272
|
case "resetContextChip":
|
|
2298
2273
|
return { ...state, contextChipVersion: state.contextChipVersion + 1 };
|
|
2299
2274
|
// --- Fleet ---
|
|
@@ -2632,7 +2607,7 @@ function App({
|
|
|
2632
2607
|
modelOptions: [],
|
|
2633
2608
|
selected: 0
|
|
2634
2609
|
},
|
|
2635
|
-
|
|
2610
|
+
confirmQueue: [],
|
|
2636
2611
|
contextChipVersion: 0,
|
|
2637
2612
|
fleet: {},
|
|
2638
2613
|
fleetCost: 0,
|
|
@@ -2646,7 +2621,7 @@ function App({
|
|
|
2646
2621
|
const inputGateRef = useRef(false);
|
|
2647
2622
|
const lastEnterAtRef = useRef(0);
|
|
2648
2623
|
const projectRoot = agent.ctx.projectRoot;
|
|
2649
|
-
const projectName =
|
|
2624
|
+
const projectName = React4.useMemo(() => {
|
|
2650
2625
|
const base = path2.basename(projectRoot);
|
|
2651
2626
|
return base && base !== path2.sep ? base : void 0;
|
|
2652
2627
|
}, [projectRoot]);
|
|
@@ -2666,13 +2641,13 @@ function App({
|
|
|
2666
2641
|
dispatch({ type: "clearInput" });
|
|
2667
2642
|
};
|
|
2668
2643
|
const startedAtRef = useRef(Date.now());
|
|
2669
|
-
const [nowTick, setNowTick] =
|
|
2644
|
+
const [nowTick, setNowTick] = React4.useState(Date.now());
|
|
2670
2645
|
useEffect(() => {
|
|
2671
2646
|
const t = setInterval(() => setNowTick(Date.now()), 1e3);
|
|
2672
2647
|
return () => clearInterval(t);
|
|
2673
2648
|
}, []);
|
|
2674
2649
|
const elapsedMs = nowTick - startedAtRef.current;
|
|
2675
|
-
const [gitInfo, setGitInfo] =
|
|
2650
|
+
const [gitInfo, setGitInfo] = React4.useState(null);
|
|
2676
2651
|
useEffect(() => {
|
|
2677
2652
|
let cancelled = false;
|
|
2678
2653
|
const refresh = () => {
|
|
@@ -2687,7 +2662,7 @@ function App({
|
|
|
2687
2662
|
clearInterval(t);
|
|
2688
2663
|
};
|
|
2689
2664
|
}, [agent.ctx.cwd]);
|
|
2690
|
-
const [lastInputTokens, setLastInputTokens] =
|
|
2665
|
+
const [lastInputTokens, setLastInputTokens] = React4.useState(0);
|
|
2691
2666
|
useEffect(() => {
|
|
2692
2667
|
const off = events.on("provider.response", (e) => {
|
|
2693
2668
|
const total = (e.usage.input ?? 0) + (e.usage.cacheRead ?? 0) + (e.usage.cacheWrite ?? 0);
|
|
@@ -2798,7 +2773,7 @@ function App({
|
|
|
2798
2773
|
const prevAnyOverlayOpen = useRef(false);
|
|
2799
2774
|
const prevEntriesCount = useRef(0);
|
|
2800
2775
|
useEffect(() => {
|
|
2801
|
-
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open ||
|
|
2776
|
+
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.confirmQueue.length > 0;
|
|
2802
2777
|
const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
|
|
2803
2778
|
const newEntryCommitted = state.entries.length > prevEntriesCount.current;
|
|
2804
2779
|
prevAnyOverlayOpen.current = anyOpenNow;
|
|
@@ -2813,7 +2788,7 @@ function App({
|
|
|
2813
2788
|
state.picker.open,
|
|
2814
2789
|
state.slashPicker.open,
|
|
2815
2790
|
state.modelPicker.open,
|
|
2816
|
-
state.
|
|
2791
|
+
state.confirmQueue.length,
|
|
2817
2792
|
state.entries.length
|
|
2818
2793
|
]);
|
|
2819
2794
|
useEffect(() => {
|
|
@@ -3193,15 +3168,6 @@ function App({
|
|
|
3193
3168
|
}
|
|
3194
3169
|
});
|
|
3195
3170
|
const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
|
|
3196
|
-
dispatch({
|
|
3197
|
-
type: "addEntry",
|
|
3198
|
-
entry: {
|
|
3199
|
-
kind: "confirm",
|
|
3200
|
-
toolName: e.tool.name,
|
|
3201
|
-
input: e.input,
|
|
3202
|
-
suggestedPattern: e.suggestedPattern
|
|
3203
|
-
}
|
|
3204
|
-
});
|
|
3205
3171
|
dispatch({
|
|
3206
3172
|
type: "confirmOpen",
|
|
3207
3173
|
info: {
|
|
@@ -3213,6 +3179,17 @@ function App({
|
|
|
3213
3179
|
}
|
|
3214
3180
|
});
|
|
3215
3181
|
});
|
|
3182
|
+
const offTrustPersisted = events.on("trust.persisted", (e) => {
|
|
3183
|
+
const icon = e.decision === "always" ? "\u2713" : "\u2717";
|
|
3184
|
+
const label = e.decision === "always" ? "always allowed" : "denied";
|
|
3185
|
+
dispatch({
|
|
3186
|
+
type: "addEntry",
|
|
3187
|
+
entry: {
|
|
3188
|
+
kind: "info",
|
|
3189
|
+
text: `${icon} ${label}: ${e.tool}(${e.pattern})`
|
|
3190
|
+
}
|
|
3191
|
+
});
|
|
3192
|
+
});
|
|
3216
3193
|
return () => {
|
|
3217
3194
|
offDelta();
|
|
3218
3195
|
offToolStart();
|
|
@@ -3222,6 +3199,7 @@ function App({
|
|
|
3222
3199
|
offProvErr();
|
|
3223
3200
|
offProvResp();
|
|
3224
3201
|
offConfirmNeeded();
|
|
3202
|
+
offTrustPersisted();
|
|
3225
3203
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
3226
3204
|
};
|
|
3227
3205
|
}, [events, agent.ctx.todos]);
|
|
@@ -3480,16 +3458,9 @@ function App({
|
|
|
3480
3458
|
process.exit(130);
|
|
3481
3459
|
}
|
|
3482
3460
|
try {
|
|
3483
|
-
exit();
|
|
3484
|
-
onExit(130);
|
|
3461
|
+
process.exit(130);
|
|
3485
3462
|
} catch {
|
|
3486
3463
|
}
|
|
3487
|
-
setTimeout(() => {
|
|
3488
|
-
try {
|
|
3489
|
-
process.exit(130);
|
|
3490
|
-
} catch {
|
|
3491
|
-
}
|
|
3492
|
-
}, 500).unref?.();
|
|
3493
3464
|
dispatch({ type: "interrupt" });
|
|
3494
3465
|
return;
|
|
3495
3466
|
}
|
|
@@ -3552,7 +3523,8 @@ function App({
|
|
|
3552
3523
|
};
|
|
3553
3524
|
}, [exit, onExit, director]);
|
|
3554
3525
|
const handleKey = async (input, key) => {
|
|
3555
|
-
if (state.status === "aborting") return;
|
|
3526
|
+
if (state.status === "aborting" && state.interrupts === 0) return;
|
|
3527
|
+
if (state.confirmQueue.length > 0) return;
|
|
3556
3528
|
if (inputGateRef.current) return;
|
|
3557
3529
|
const isEnter = key.return || input === "\r" || input === "\n";
|
|
3558
3530
|
if (state.modelPicker.open) {
|
|
@@ -3666,7 +3638,7 @@ function App({
|
|
|
3666
3638
|
return;
|
|
3667
3639
|
}
|
|
3668
3640
|
}
|
|
3669
|
-
if (key.escape && state.status !== "idle" &&
|
|
3641
|
+
if (key.escape && state.status !== "idle" && state.confirmQueue.length === 0) {
|
|
3670
3642
|
const runningTools = Array.from(state.runningTools.values()).map((t) => t.name);
|
|
3671
3643
|
const subagents = Object.values(state.fleet).filter((e) => e.status === "running").map((e) => ({
|
|
3672
3644
|
label: e.name,
|
|
@@ -4034,7 +4006,7 @@ function App({
|
|
|
4034
4006
|
value: state.buffer,
|
|
4035
4007
|
cursor: state.cursor,
|
|
4036
4008
|
placeholders: state.placeholders,
|
|
4037
|
-
disabled: state.status === "aborting",
|
|
4009
|
+
disabled: state.status === "aborting" || state.confirmQueue.length > 0,
|
|
4038
4010
|
hint: inputHint,
|
|
4039
4011
|
onKey: handleKey
|
|
4040
4012
|
}
|
|
@@ -4066,15 +4038,24 @@ function App({
|
|
|
4066
4038
|
hint: state.modelPicker.hint
|
|
4067
4039
|
}
|
|
4068
4040
|
) : null,
|
|
4069
|
-
state.
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4041
|
+
state.confirmQueue.length > 0 && (() => {
|
|
4042
|
+
const head = state.confirmQueue[0];
|
|
4043
|
+
let resolved = false;
|
|
4044
|
+
return /* @__PURE__ */ jsx(
|
|
4045
|
+
ConfirmPrompt,
|
|
4046
|
+
{
|
|
4047
|
+
toolName: head.toolName,
|
|
4048
|
+
input: head.input,
|
|
4049
|
+
suggestedPattern: head.suggestedPattern,
|
|
4050
|
+
onDecision: (decision) => {
|
|
4051
|
+
if (resolved) return;
|
|
4052
|
+
resolved = true;
|
|
4053
|
+
head.resolve(decision);
|
|
4054
|
+
dispatch({ type: "confirmClose" });
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
);
|
|
4058
|
+
})(),
|
|
4078
4059
|
/* @__PURE__ */ jsx(
|
|
4079
4060
|
StatusBar,
|
|
4080
4061
|
{
|
|
@@ -4206,7 +4187,7 @@ async function runTui(opts) {
|
|
|
4206
4187
|
let instance;
|
|
4207
4188
|
try {
|
|
4208
4189
|
instance = render(
|
|
4209
|
-
|
|
4190
|
+
React4.createElement(App, {
|
|
4210
4191
|
agent: opts.agent,
|
|
4211
4192
|
slashRegistry: opts.slashRegistry,
|
|
4212
4193
|
attachments: opts.attachments,
|