@wrongstack/tui 0.82.6 → 0.87.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.js CHANGED
@@ -8,6 +8,7 @@ import { routeImagesForModel } from '@wrongstack/runtime/vision';
8
8
  import { getIndexState, onIndexStateChange, getProcessRegistry } from '@wrongstack/tools';
9
9
  import { readClipboardImage } from '@wrongstack/runtime/clipboard';
10
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
+ import { expectDefined as expectDefined$1 } from '@wrongstack/core/utils';
11
12
  import { spawn } from 'child_process';
12
13
 
13
14
  // src/run-tui.ts
@@ -1337,6 +1338,47 @@ function EnhancePanel({
1337
1338
  ] }) })
1338
1339
  ] });
1339
1340
  }
1341
+ function EscConfirmPrompt({
1342
+ runningTools,
1343
+ subagentCount,
1344
+ onConfirm,
1345
+ onCancel
1346
+ }) {
1347
+ useInput((input, key) => {
1348
+ if (key.escape) {
1349
+ onCancel();
1350
+ return;
1351
+ }
1352
+ const ch = input.toLowerCase();
1353
+ if (ch === "y" || key.return) {
1354
+ onConfirm();
1355
+ } else if (ch === "n") {
1356
+ onCancel();
1357
+ }
1358
+ });
1359
+ const running = runningTools.length;
1360
+ const toolLabel = running === 1 ? runningTools[0] : null;
1361
+ const toolHint = toolLabel ? ` (${toolLabel})` : running > 1 ? ` (${running} tools)` : "";
1362
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1, children: [
1363
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u23F8 Interrupt the current run?" }) }),
1364
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1365
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1366
+ "The agent is working",
1367
+ toolHint,
1368
+ ".",
1369
+ subagentCount > 0 ? ` ${subagentCount} subagent${subagentCount === 1 ? "" : "s"} active.` : ""
1370
+ ] }),
1371
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Stop to give a new direction now, or let it finish." })
1372
+ ] }),
1373
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
1374
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
1375
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[y]" }),
1376
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "es \u2014 stop and steer " }),
1377
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[n]" }),
1378
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "o / Esc \u2014 keep running" })
1379
+ ] }) })
1380
+ ] });
1381
+ }
1340
1382
  function FilePicker({ query, matches, selected }) {
1341
1383
  if (matches.length === 0) {
1342
1384
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
@@ -1933,14 +1975,6 @@ function detectLang(fenceInfo) {
1933
1975
  const tag = fenceInfo.trim().toLowerCase().split(/\s+/)[0] ?? "";
1934
1976
  return LANG_ALIASES[tag] ?? "plain";
1935
1977
  }
1936
-
1937
- // src/markdown-table.ts
1938
- function expectDefined2(value) {
1939
- if (value === null || value === void 0) {
1940
- throw new Error("Expected value to be defined");
1941
- }
1942
- return value;
1943
- }
1944
1978
  var ROW_RE = /^\s*\|.*\|\s*$/;
1945
1979
  var SEP_RE = /^\s*\|[\s\-:|]+\|\s*$/;
1946
1980
  function detectTable(lines, start) {
@@ -2024,14 +2058,14 @@ function computeWidths(allRows, cols, maxWidth, sepWidths) {
2024
2058
  const stripped = stripInlineMarkers(cell);
2025
2059
  const w = longestWord(stripped);
2026
2060
  const total = strWidth(stripped);
2027
- natural[c] = Math.max(expectDefined2(natural[c]), w, total);
2061
+ natural[c] = Math.max(expectDefined$1(natural[c]), w, total);
2028
2062
  }
2029
2063
  }
2030
2064
  if (sepWidths) {
2031
2065
  for (let c = 0; c < cols && c < sepWidths.length; c++) {
2032
2066
  const sepW = sepWidths[c];
2033
2067
  if (sepW != null) {
2034
- natural[c] = Math.max(expectDefined2(natural[c]), sepW);
2068
+ natural[c] = Math.max(expectDefined$1(natural[c]), sepW);
2035
2069
  }
2036
2070
  }
2037
2071
  }
@@ -2043,7 +2077,7 @@ function computeWidths(allRows, cols, maxWidth, sepWidths) {
2043
2077
  let maxIdx = -1;
2044
2078
  let maxVal = MIN_COL_WIDTH;
2045
2079
  for (let i = 0; i < cols; i++) {
2046
- const w = expectDefined2(widths[i]);
2080
+ const w = expectDefined$1(widths[i]);
2047
2081
  if (w > maxVal) {
2048
2082
  maxVal = w;
2049
2083
  maxIdx = i;
@@ -2103,7 +2137,7 @@ function strWidth(s2) {
2103
2137
  if (i < len) i++;
2104
2138
  continue;
2105
2139
  }
2106
- const code = expectDefined2(s2.codePointAt(i));
2140
+ const code = expectDefined$1(s2.codePointAt(i));
2107
2141
  const cpLen = code > 65535 ? 2 : 1;
2108
2142
  if (code === 8205 || // ZWJ — Zero Width Joiner (emoji sequences)
2109
2143
  code === 8203 || // ZWSP — Zero Width Space
@@ -2134,7 +2168,6 @@ function strWidth(s2) {
2134
2168
  code >= 8960 && code <= 9215 || // Miscellaneous Technical
2135
2169
  code >= 11088 && code <= 11093 || // Stars and similar
2136
2170
  code >= 10548 && code <= 10549 || // Arrow forms
2137
- code >= 8592 && code <= 8703 || // Arrows
2138
2171
  code >= 9632 && code <= 9727 || // Geometric Shapes
2139
2172
  code >= 9664 && code <= 9726 || // More Geometric Shapes (includes ▶)
2140
2173
  code >= 9984 && code <= 10175) {
@@ -4292,7 +4325,7 @@ function SettingsPicker({
4292
4325
  {
4293
4326
  label: "Confirm before exit",
4294
4327
  value: boolVal(confirmExit),
4295
- detail: "Ask for confirmation on Ctrl+C exit"
4328
+ detail: "Confirmation on Esc interrupt & Ctrl+C exit"
4296
4329
  },
4297
4330
  {
4298
4331
  label: "Next-step prediction",
@@ -4427,6 +4460,7 @@ function SettingsPicker({
4427
4460
  hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
4428
4461
  ] });
4429
4462
  }
4463
+ var MAX_VISIBLE_ITEMS = 8;
4430
4464
  function SlashMenu({ query, matches, selected }) {
4431
4465
  const placeholder = query ? `/${query}` : "/";
4432
4466
  const rows = [];
@@ -4439,12 +4473,26 @@ function SlashMenu({ query, matches, selected }) {
4439
4473
  }
4440
4474
  rows.push({ type: "item", match: m, index: i });
4441
4475
  }
4442
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
4476
+ const selectedRowIdx = rows.findIndex((r) => r.type === "item" && r.index === selected);
4477
+ const visible = windowRows(rows, selectedRowIdx < 0 ? 0 : selectedRowIdx, MAX_VISIBLE_ITEMS);
4478
+ const hiddenAbove = visible.start;
4479
+ const hiddenBelow = rows.length - visible.end;
4480
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
4443
4481
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4444
- placeholder || "/",
4445
- " \u2014 \u2191/\u2193 select, Enter dispatch, Tab autocomplete, Esc close"
4482
+ placeholder,
4483
+ " ",
4484
+ matches.length > 0 ? `(${selected + 1}/${matches.length})` : ""
4485
+ ] }),
4486
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4487
+ " \u2191 ",
4488
+ hiddenAbove,
4489
+ " more"
4490
+ ] }),
4491
+ visible.contextHeader && /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
4492
+ " ",
4493
+ visible.contextHeader
4446
4494
  ] }),
4447
- rows.map((row) => {
4495
+ visible.rows.map((row) => {
4448
4496
  if (row.type === "header") {
4449
4497
  return /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
4450
4498
  " ",
@@ -4465,9 +4513,41 @@ function SlashMenu({ query, matches, selected }) {
4465
4513
  ] })
4466
4514
  ] }, m.name);
4467
4515
  }),
4468
- matches.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" })
4516
+ hiddenBelow > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4517
+ " \u2193 ",
4518
+ hiddenBelow,
4519
+ " more"
4520
+ ] }),
4521
+ matches.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" }),
4522
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500 \u2191\u2193 nav \xB7 Enter run \xB7 Tab fill \xB7 Esc close" })
4469
4523
  ] });
4470
4524
  }
4525
+ function windowRows(rows, focus, max) {
4526
+ if (rows.length <= max) {
4527
+ return { rows, start: 0, end: rows.length, contextHeader: null };
4528
+ }
4529
+ let start = focus - Math.floor(max / 2);
4530
+ if (start < 0) start = 0;
4531
+ let end = start + max;
4532
+ if (end > rows.length) {
4533
+ end = rows.length;
4534
+ start = end - max;
4535
+ }
4536
+ let contextHeader = null;
4537
+ if (start > 0) {
4538
+ const first = rows[start];
4539
+ if (first && first.type === "item") {
4540
+ for (let i = start - 1; i >= 0; i--) {
4541
+ const r = rows[i];
4542
+ if (r && r.type === "header") {
4543
+ contextHeader = r.category;
4544
+ break;
4545
+ }
4546
+ }
4547
+ }
4548
+ }
4549
+ return { rows: rows.slice(start, end), start, end, contextHeader };
4550
+ }
4471
4551
  function TodosMonitor({ todos }) {
4472
4552
  const { stdout } = useStdout();
4473
4553
  const done = todos.filter((t) => t.status === "completed").length;
@@ -5728,6 +5808,10 @@ function reducer(state, action) {
5728
5808
  return { ...state, enhanceEnabled: action.enabled };
5729
5809
  case "enhanceBusy":
5730
5810
  return { ...state, enhanceBusy: action.on };
5811
+ case "escConfirmOpen":
5812
+ return { ...state, escConfirm: { snapshot: action.snapshot } };
5813
+ case "escConfirmClose":
5814
+ return { ...state, escConfirm: null };
5731
5815
  case "resetContextChip":
5732
5816
  return { ...state, contextChipVersion: state.contextChipVersion + 1 };
5733
5817
  // --- Fleet ---
@@ -6404,7 +6488,7 @@ function App({
6404
6488
  confirmExit = true,
6405
6489
  enhanceEnabled = true,
6406
6490
  enhanceController,
6407
- enhanceDelayMs = 4e3,
6491
+ enhanceDelayMs = 15e3,
6408
6492
  getYolo,
6409
6493
  getAutonomy,
6410
6494
  getEternalEngine,
@@ -6542,6 +6626,7 @@ function App({
6542
6626
  enhance: null,
6543
6627
  enhanceEnabled,
6544
6628
  enhanceBusy: false,
6629
+ escConfirm: null,
6545
6630
  contextChipVersion: 0,
6546
6631
  fleet: {},
6547
6632
  leader: {
@@ -6764,7 +6849,7 @@ function App({
6764
6849
  }
6765
6850
  }, []);
6766
6851
  React6.useLayoutEffect(() => {
6767
- const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.settingsPicker.open || state.enhanceBusy || state.enhance != null || state.confirmQueue.length > 0;
6852
+ const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.settingsPicker.open || state.enhanceBusy || state.enhance != null || state.escConfirm != null || state.confirmQueue.length > 0;
6768
6853
  const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
6769
6854
  const newEntryCommitted = state.entries.length > prevEntriesCount.current;
6770
6855
  const curToolStreamLen = state.toolStream?.text.length ?? 0;
@@ -6783,6 +6868,7 @@ function App({
6783
6868
  state.settingsPicker.open,
6784
6869
  state.enhanceBusy,
6785
6870
  state.enhance,
6871
+ state.escConfirm,
6786
6872
  state.confirmQueue.length,
6787
6873
  state.entries.length,
6788
6874
  state.toolStream?.text,
@@ -8073,6 +8159,7 @@ function App({
8073
8159
  return;
8074
8160
  }
8075
8161
  if (state.enhance) return;
8162
+ if (state.escConfirm) return;
8076
8163
  if (state.helpOpen) {
8077
8164
  if (key.escape || input === "?" || input === "q") dispatch({ type: "toggleHelp" });
8078
8165
  return;
@@ -8312,17 +8399,26 @@ function App({
8312
8399
  }));
8313
8400
  const subagentsTerminated = subagents.length;
8314
8401
  const partialAssistantText = streamingTextRef.current.slice(-1500);
8402
+ const snapshot = {
8403
+ runningTools,
8404
+ subagents,
8405
+ subagentsTerminated,
8406
+ partialAssistantText
8407
+ };
8408
+ if (confirmExitRef.current) {
8409
+ dispatch({ type: "escConfirmOpen", snapshot });
8410
+ dispatch({
8411
+ type: "addEntry",
8412
+ entry: {
8413
+ kind: "warn",
8414
+ text: `\u23F8 Interrupt? [y]es \u2014 stop and steer \xB7 [n]o / Esc \u2014 keep running` + (subagentsTerminated > 0 ? ` (${subagentsTerminated} subagent${subagentsTerminated === 1 ? "" : "s"})` : "")
8415
+ }
8416
+ });
8417
+ return;
8418
+ }
8315
8419
  activeCtrlRef.current?.abort();
8316
8420
  dispatch({ type: "status", status: "aborting" });
8317
- dispatch({
8318
- type: "steerStart",
8319
- snapshot: {
8320
- runningTools,
8321
- subagents,
8322
- subagentsTerminated,
8323
- partialAssistantText
8324
- }
8325
- });
8421
+ dispatch({ type: "steerStart", snapshot });
8326
8422
  if (director && subagentsTerminated > 0) {
8327
8423
  const cap = new Promise((resolve) => {
8328
8424
  const t = setTimeout(resolve, 1500);
@@ -9102,14 +9198,14 @@ User message:
9102
9198
  ),
9103
9199
  /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [
9104
9200
  /* @__PURE__ */ jsx(LiveActivityStrip, { entries: state.fleet, nowTick }),
9105
- /* @__PURE__ */ jsx(
9201
+ enhanceActive ? /* @__PURE__ */ jsx(Box, { height: 1 }) : /* @__PURE__ */ jsx(
9106
9202
  Input,
9107
9203
  {
9108
9204
  prompt: INPUT_PROMPT,
9109
- value: enhanceActive ? "" : state.buffer,
9110
- cursor: enhanceActive ? 0 : state.cursor,
9205
+ value: state.buffer,
9206
+ cursor: state.cursor,
9111
9207
  disabled: state.status === "aborting" && !state.steeringPending || state.confirmQueue.length > 0,
9112
- hint: enhanceActive ? "" : inputHint,
9208
+ hint: inputHint,
9113
9209
  onKey: handleKey
9114
9210
  }
9115
9211
  ),
@@ -9221,6 +9317,41 @@ User message:
9221
9317
  }
9222
9318
  );
9223
9319
  })(),
9320
+ state.escConfirm ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, flexShrink: 0, children: /* @__PURE__ */ jsx(
9321
+ EscConfirmPrompt,
9322
+ {
9323
+ runningTools: state.escConfirm.snapshot.runningTools,
9324
+ subagentCount: state.escConfirm.snapshot.subagentsTerminated,
9325
+ onConfirm: () => {
9326
+ const { snapshot } = state.escConfirm;
9327
+ activeCtrlRef.current?.abort();
9328
+ dispatch({ type: "status", status: "aborting" });
9329
+ dispatch({ type: "steerStart", snapshot });
9330
+ if (director && snapshot.subagentsTerminated > 0) {
9331
+ const cap = new Promise((resolve) => {
9332
+ const t = setTimeout(resolve, 1500);
9333
+ t.unref?.();
9334
+ });
9335
+ void Promise.race([director.terminateAll().catch(() => void 0), cap]);
9336
+ }
9337
+ const droppedCount = state.queue.length;
9338
+ if (droppedCount > 0) dispatch({ type: "queueClear" });
9339
+ const droppedTag = droppedCount > 0 ? ` \xB7 dropped ${droppedCount} queued` : "";
9340
+ const fleetTag = snapshot.subagentsTerminated > 0 ? ` \xB7 stopped ${snapshot.subagentsTerminated} subagent${snapshot.subagentsTerminated === 1 ? "" : "s"}` : "";
9341
+ dispatch({
9342
+ type: "addEntry",
9343
+ entry: {
9344
+ kind: "warn",
9345
+ text: `\u21AF Interrupted${droppedTag}${fleetTag}. Type your new direction.`
9346
+ }
9347
+ });
9348
+ dispatch({ type: "escConfirmClose" });
9349
+ },
9350
+ onCancel: () => {
9351
+ dispatch({ type: "escConfirmClose" });
9352
+ }
9353
+ }
9354
+ ) }) : null,
9224
9355
  state.enhanceBusy && !state.enhance ? /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u2728 refining your request\u2026" }) }) : null,
9225
9356
  state.enhance ? (() => {
9226
9357
  const info = state.enhance;