@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { render, useApp, Box, useStdout, Static, Text, useInput, useStdin } from 'ink';
2
- import React, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
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
- useInput((_, key) => {
43
- if (key.return) {
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 (key.escape) {
50
+ } else if (ch === "n") {
46
51
  onDecision("no");
47
- } else if (key.ctrl && _.toLowerCase() === "a") {
52
+ } else if (ch === "a") {
48
53
  onDecision("always");
49
- } else if (key.ctrl && _.toLowerCase() === "d") {
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: "single",
62
- borderTop: false,
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 Confirm" }),
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: "[\u21B5]" }),
78
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
79
- /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
80
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
81
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
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
- " always (",
86
+ "lways (",
84
87
  suggestedPattern,
85
88
  ") "
86
89
  ] }),
87
- /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
88
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
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
- Box,
616
- {
617
- flexDirection: "column",
618
- borderStyle: "single",
619
- borderTop: false,
620
- borderLeft: false,
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, confirm: action.info };
2269
+ return { ...state, confirmQueue: [...state.confirmQueue, action.info] };
2295
2270
  case "confirmClose":
2296
- return { ...state, confirm: null };
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
- confirm: null,
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 = React.useMemo(() => {
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] = React.useState(Date.now());
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] = React.useState(null);
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] = React.useState(0);
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 || !!state.confirm;
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.confirm,
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" && !state.confirm) {
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.confirm ? /* @__PURE__ */ jsx(
4070
- ConfirmPrompt,
4071
- {
4072
- toolName: state.confirm.toolName,
4073
- input: state.confirm.input,
4074
- suggestedPattern: state.confirm.suggestedPattern,
4075
- onDecision: state.confirm.resolve
4076
- }
4077
- ) : null,
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
- React.createElement(App, {
4190
+ React4.createElement(App, {
4210
4191
  agent: opts.agent,
4211
4192
  slashRegistry: opts.slashRegistry,
4212
4193
  attachments: opts.attachments,