@wrongstack/tui 0.6.4 → 0.6.6

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 CHANGED
@@ -31,19 +31,53 @@ interface RunTuiOptions {
31
31
  /** Query live YOLO state from the permission policy. */
32
32
  getYolo?: () => boolean;
33
33
  /** Query the live autonomy mode. */
34
- getAutonomy?: () => 'off' | 'suggest' | 'auto' | 'eternal';
34
+ getAutonomy?: () => 'off' | 'suggest' | 'auto' | 'eternal' | 'eternal-parallel';
35
35
  /**
36
36
  * Access the eternal-autonomy engine. When autonomy mode flips to
37
37
  * 'eternal' the TUI drives `runOneIteration()` from the post-slash hook
38
38
  * so the engine and TUI never race for the shared Context.
39
39
  */
40
40
  getEternalEngine?: () => _wrongstack_core.EternalAutonomyEngine | null;
41
+ /**
42
+ * Access the parallel-eternal engine. When autonomy mode flips to
43
+ * 'eternal-parallel' the TUI drives `runOneIteration()` from the post-slash
44
+ * hook so the engine and TUI never race for the shared Context.
45
+ */
46
+ getParallelEngine?: () => _wrongstack_core.ParallelEternalEngine | null;
41
47
  /**
42
48
  * Subscribe to live per-iteration events from the eternal engine.
43
49
  * Returns an unsubscribe function. TUI uses this to render each
44
50
  * iteration as a live timeline entry as it lands.
45
51
  */
46
52
  subscribeEternalIteration?: (fn: (entry: _wrongstack_core.JournalEntry) => void) => () => void;
53
+ /**
54
+ * Subscribe to per-iteration stage transitions from the eternal engine.
55
+ * TUI uses this to render live status (decide → execute → reflect →
56
+ * sleep/paused/stopped) in the status bar.
57
+ */
58
+ subscribeEternalStage?: (fn: (stage: {
59
+ phase: 'idle';
60
+ } | {
61
+ phase: 'decide';
62
+ reason: string;
63
+ } | {
64
+ phase: 'execute';
65
+ task: string;
66
+ } | {
67
+ phase: 'reflect';
68
+ status: 'success' | 'failure' | 'aborted' | 'skipped';
69
+ note?: string;
70
+ } | {
71
+ phase: 'sleep';
72
+ ms: number;
73
+ } | {
74
+ phase: 'paused';
75
+ } | {
76
+ phase: 'stopped';
77
+ } | {
78
+ phase: 'error';
79
+ message: string;
80
+ }) => void) => () => void;
47
81
  /** Renders in the startup banner. Read from the CLI's package.json. */
48
82
  appVersion?: string;
49
83
  /** Provider id for the startup banner ("openai", "anthropic", ...). */
@@ -56,6 +90,8 @@ interface RunTuiOptions {
56
90
  getPickableProviders?: () => Promise<ProviderOption[]>;
57
91
  /** Apply a (provider, model) pair after the picker confirms. Returns an error string on failure. */
58
92
  switchProviderAndModel?: (providerId: string, modelId: string) => string | null;
93
+ /** Apply an autonomy mode after the picker confirms. Returns an error string on failure. */
94
+ switchAutonomy?: (mode: 'off' | 'suggest' | 'auto' | 'eternal' | 'eternal-parallel') => string | null;
59
95
  /**
60
96
  * Model-specific maxContext (tokens), resolved by the CLI via the
61
97
  * ModelsRegistry. When omitted, the TUI falls back to the provider
package/dist/index.js CHANGED
@@ -1550,6 +1550,25 @@ function LiveActivityStrip({
1550
1550
  ] }) }) : null
1551
1551
  ] });
1552
1552
  }
1553
+ var AUTONOMY_OPTIONS = [
1554
+ { mode: "off", label: "OFF", description: "Agent stops after each turn (normal interactive mode)", color: "green" },
1555
+ { mode: "suggest", label: "SUGGEST", description: "Shows next-step suggestions after each turn", color: "cyan" },
1556
+ { mode: "auto", label: "AUTO", description: "Self-driving \u2014 agent picks next step and continues", color: "yellow" },
1557
+ { mode: "eternal", label: "ETERNAL", description: "Goal-driven loop \u2014 requires /goal set first", color: "red" },
1558
+ { mode: "eternal-parallel", label: "PARALLEL", description: "Fan-out 4\u20138 subagents per tick \u2014 requires /goal", color: "magenta" }
1559
+ ];
1560
+ function AutonomyPicker({ options, selected, hint }) {
1561
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
1562
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Autonomy Mode \u2501\u2501" }),
1563
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
1564
+ options.map((opt, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? opt.color : void 0, inverse: i === selected, children: [
1565
+ i === selected ? "\u203A " : " ",
1566
+ /* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
1567
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description })
1568
+ ] }, opt.mode)),
1569
+ hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
1570
+ ] });
1571
+ }
1553
1572
  function ModelPicker({
1554
1573
  step,
1555
1574
  providerOptions,
@@ -1634,7 +1653,8 @@ function StatusBar({
1634
1653
  context,
1635
1654
  projectName,
1636
1655
  processCount,
1637
- hiddenItems
1656
+ hiddenItems,
1657
+ eternalStage
1638
1658
  }) {
1639
1659
  const hiddenSet = new Set(hiddenItems);
1640
1660
  const usage = tokenCounter?.total();
@@ -1730,8 +1750,12 @@ function StatusBar({
1730
1750
  }
1731
1751
  )
1732
1752
  ] }) : null,
1753
+ eternalStage ? /* @__PURE__ */ jsxs(Fragment, { children: [
1754
+ yolo || autonomy && autonomy !== "off" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
1755
+ /* @__PURE__ */ jsx(EternalStageChip, { stage: eternalStage })
1756
+ ] }) : null,
1733
1757
  elapsedMs !== void 0 && !hiddenSet.has("elapsed") ? /* @__PURE__ */ jsxs(Fragment, { children: [
1734
- yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
1758
+ yolo || autonomy && autonomy !== "off" || eternalStage ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
1735
1759
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1736
1760
  "\u23F1 ",
1737
1761
  fmtElapsed2(elapsedMs)
@@ -1855,6 +1879,45 @@ function StatusBar({
1855
1879
  }
1856
1880
  );
1857
1881
  }
1882
+ function EternalStageChip({
1883
+ stage
1884
+ }) {
1885
+ switch (stage.phase) {
1886
+ case "idle":
1887
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2B1C idle" });
1888
+ case "decide":
1889
+ return /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
1890
+ "\u2B07 decide: ",
1891
+ stage.reason
1892
+ ] });
1893
+ case "execute":
1894
+ return /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1895
+ "\u25B6 ",
1896
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "execute" }),
1897
+ stage.task ? `(${stage.task})` : ""
1898
+ ] });
1899
+ case "reflect":
1900
+ return /* @__PURE__ */ jsxs(Text, { color: stage.status === "success" ? "green" : stage.status === "failure" ? "red" : "yellow", children: [
1901
+ "\u21A9 reflect: ",
1902
+ stage.status
1903
+ ] });
1904
+ case "sleep":
1905
+ return /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1906
+ "\u{1F4A4} sleep ",
1907
+ Math.round(stage.ms / 1e3),
1908
+ "s"
1909
+ ] });
1910
+ case "paused":
1911
+ return /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u23F8 paused" });
1912
+ case "stopped":
1913
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25A0 stopped" });
1914
+ case "error":
1915
+ return /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1916
+ "\u26A0 error: ",
1917
+ stage.message
1918
+ ] });
1919
+ }
1920
+ }
1858
1921
  function ContextChip({ ctx }) {
1859
1922
  const ratio = Math.max(0, Math.min(1, ctx.used / ctx.max));
1860
1923
  const pct = Math.round(ratio * 100);
@@ -2451,6 +2514,30 @@ function reducer(state, action) {
2451
2514
  ...state,
2452
2515
  modelPicker: { ...state.modelPicker, hint: action.text }
2453
2516
  };
2517
+ case "autonomyPickerOpen":
2518
+ return {
2519
+ ...state,
2520
+ autonomyPicker: { open: true, options: action.options, selected: 0, hint: void 0 }
2521
+ };
2522
+ case "autonomyPickerClose":
2523
+ return {
2524
+ ...state,
2525
+ autonomyPicker: { open: false, options: [], selected: 0 }
2526
+ };
2527
+ case "autonomyPickerMove": {
2528
+ const n = state.autonomyPicker.options.length;
2529
+ if (n === 0) return state;
2530
+ const next = (state.autonomyPicker.selected + action.delta + n) % n;
2531
+ return {
2532
+ ...state,
2533
+ autonomyPicker: { ...state.autonomyPicker, selected: next }
2534
+ };
2535
+ }
2536
+ case "autonomyPickerHint":
2537
+ return {
2538
+ ...state,
2539
+ autonomyPicker: { ...state.autonomyPicker, hint: action.text }
2540
+ };
2454
2541
  case "confirmOpen":
2455
2542
  return { ...state, confirmQueue: [...state.confirmQueue, action.info] };
2456
2543
  case "confirmClose":
@@ -2666,6 +2753,9 @@ function reducer(state, action) {
2666
2753
  rewindOverlay: null
2667
2754
  };
2668
2755
  }
2756
+ case "eternalStage": {
2757
+ return { ...state, eternalStage: action.stage };
2758
+ }
2669
2759
  }
2670
2760
  }
2671
2761
  var PASTE_THRESHOLD_CHARS = 200;
@@ -2718,7 +2808,9 @@ function App({
2718
2808
  getYolo,
2719
2809
  getAutonomy,
2720
2810
  getEternalEngine,
2811
+ getParallelEngine,
2721
2812
  subscribeEternalIteration,
2813
+ subscribeEternalStage,
2722
2814
  getSDDContext,
2723
2815
  onSDDOutput,
2724
2816
  appVersion,
@@ -2727,6 +2819,7 @@ function App({
2727
2819
  keyTail,
2728
2820
  getPickableProviders,
2729
2821
  switchProviderAndModel,
2822
+ switchAutonomy,
2730
2823
  effectiveMaxContext,
2731
2824
  onExit,
2732
2825
  director,
@@ -2789,13 +2882,15 @@ function App({
2789
2882
  modelOptions: [],
2790
2883
  selected: 0
2791
2884
  },
2885
+ autonomyPicker: { open: false, options: [], selected: 0 },
2792
2886
  confirmQueue: [],
2793
2887
  contextChipVersion: 0,
2794
2888
  fleet: {},
2795
2889
  fleetCost: 0,
2796
2890
  streamFleet: true,
2797
2891
  checkpoints: [],
2798
- rewindOverlay: null
2892
+ rewindOverlay: null,
2893
+ eternalStage: null
2799
2894
  });
2800
2895
  const builderRef = useRef(null);
2801
2896
  if (builderRef.current === null) {
@@ -3282,6 +3377,22 @@ function App({
3282
3377
  slashRegistry.unregister("model");
3283
3378
  };
3284
3379
  }, [slashRegistry, getPickableProviders, switchProviderAndModel]);
3380
+ useEffect(() => {
3381
+ if (!switchAutonomy) return;
3382
+ const cmd = {
3383
+ name: "autonomy",
3384
+ aliases: ["auto"],
3385
+ description: "Pick an autonomy mode interactively (picker).",
3386
+ async run() {
3387
+ dispatch({ type: "autonomyPickerOpen", options: AUTONOMY_OPTIONS });
3388
+ return { message: void 0 };
3389
+ }
3390
+ };
3391
+ slashRegistry.register(cmd);
3392
+ return () => {
3393
+ slashRegistry.unregister("autonomy");
3394
+ };
3395
+ }, [slashRegistry, switchAutonomy]);
3285
3396
  useEffect(() => {
3286
3397
  const FLUSH_MS = 100;
3287
3398
  const flush = () => {
@@ -3471,6 +3582,7 @@ function App({
3471
3582
  const offBudgetWarning = events.on("subagent.budget_warning", (e) => {
3472
3583
  const lbl = labelFor(e.subagentId);
3473
3584
  dispatch({ type: "fleetBudgetWarning", id: e.subagentId, kind: e.kind, used: e.used, limit: e.limit });
3585
+ const timeoutSuffix = e.kind === "timeout" ? " (subagent continues running)" : " \u2014 extending";
3474
3586
  dispatch({
3475
3587
  type: "addEntry",
3476
3588
  entry: {
@@ -3478,12 +3590,27 @@ function App({
3478
3590
  agentLabel: lbl.label,
3479
3591
  agentColor: lbl.color,
3480
3592
  icon: "\u26A1",
3481
- text: `hitting ${e.kind} limit (${e.used}/${e.limit}) \u2014 extending`
3593
+ text: `hitting ${e.kind} limit (${e.used}/${e.limit})${timeoutSuffix}`
3594
+ }
3595
+ });
3596
+ });
3597
+ const offIterationSummary = events.on("subagent.iteration_summary", (e) => {
3598
+ const lbl = labelFor(e.subagentId);
3599
+ const costStr = e.costUsd > 0 ? ` \xB7 ${e.costUsd.toFixed(3)}` : "";
3600
+ const toolStr = e.currentTool ? ` \xB7 doing ${e.currentTool}` : "";
3601
+ const partial = e.partialText ? ` \xB7 "${e.partialText.slice(0, 60)}${e.partialText.length > 60 ? "\u2026" : ""}"` : "";
3602
+ dispatch({
3603
+ type: "addEntry",
3604
+ entry: {
3605
+ kind: "subagent",
3606
+ agentLabel: lbl.label,
3607
+ agentColor: lbl.color,
3608
+ icon: "\u{1F4AC}",
3609
+ text: `L${e.iteration} \xB7 ${e.toolCalls} tools${costStr}${toolStr}${partial}`
3482
3610
  }
3483
3611
  });
3484
3612
  });
3485
3613
  const offTool = events.on("subagent.tool_executed", (e) => {
3486
- if (director) return;
3487
3614
  dispatch({
3488
3615
  type: "fleetTool",
3489
3616
  id: e.subagentId,
@@ -3499,6 +3626,7 @@ function App({
3499
3626
  offStarted();
3500
3627
  offCompleted();
3501
3628
  offBudgetWarning();
3629
+ offIterationSummary();
3502
3630
  offTool();
3503
3631
  };
3504
3632
  }, [events, director]);
@@ -3912,6 +4040,32 @@ function App({
3912
4040
  }
3913
4041
  return;
3914
4042
  }
4043
+ if (state.autonomyPicker.open) {
4044
+ if (key.escape) {
4045
+ dispatch({ type: "autonomyPickerClose" });
4046
+ return;
4047
+ }
4048
+ if (key.upArrow) {
4049
+ dispatch({ type: "autonomyPickerMove", delta: -1 });
4050
+ return;
4051
+ }
4052
+ if (key.downArrow) {
4053
+ dispatch({ type: "autonomyPickerMove", delta: 1 });
4054
+ return;
4055
+ }
4056
+ if (isEnter) {
4057
+ const opt = state.autonomyPicker.options[state.autonomyPicker.selected];
4058
+ if (!opt) return;
4059
+ const err = switchAutonomy?.(opt.mode);
4060
+ if (err) {
4061
+ dispatch({ type: "autonomyPickerHint", text: err });
4062
+ return;
4063
+ }
4064
+ dispatch({ type: "autonomyPickerClose" });
4065
+ return;
4066
+ }
4067
+ return;
4068
+ }
3915
4069
  if (state.slashPicker.open) {
3916
4070
  if (key.escape) {
3917
4071
  dispatch({ type: "slashPickerClose" });
@@ -4111,6 +4265,18 @@ function App({
4111
4265
  setDraft(next2, lastWordStart);
4112
4266
  return;
4113
4267
  }
4268
+ if (key.delete || key.ctrl && input === "d") {
4269
+ if (cursor >= buffer.length) return;
4270
+ const next2 = buffer.slice(0, cursor) + buffer.slice(cursor + 1);
4271
+ setDraft(next2, cursor);
4272
+ return;
4273
+ }
4274
+ if (key.ctrl && input === "k") {
4275
+ if (cursor >= buffer.length) return;
4276
+ const next2 = buffer.slice(0, cursor);
4277
+ setDraft(next2, cursor);
4278
+ return;
4279
+ }
4114
4280
  if (key.meta && input === "v") {
4115
4281
  await pasteClipboardImage();
4116
4282
  return;
@@ -4269,6 +4435,39 @@ function App({
4269
4435
  const eternalLoopRunningRef = useRef(false);
4270
4436
  const runEternalLoopRef = useRef(runEternalLoop);
4271
4437
  runEternalLoopRef.current = runEternalLoop;
4438
+ const runParallelLoop = async () => {
4439
+ const engine = getParallelEngine?.();
4440
+ if (!engine) return;
4441
+ if (parallelLoopRunningRef.current) return;
4442
+ parallelLoopRunningRef.current = true;
4443
+ try {
4444
+ while (true) {
4445
+ const liveMode = getAutonomy?.() ?? "off";
4446
+ if (liveMode !== "eternal-parallel") break;
4447
+ if (engine.currentState === "stopped") break;
4448
+ dispatch({ type: "status", status: "running" });
4449
+ try {
4450
+ await engine.runOneIteration();
4451
+ } catch (err) {
4452
+ dispatch({
4453
+ type: "addEntry",
4454
+ entry: { kind: "error", text: `[parallel] ${err instanceof Error ? err.message : String(err)}` }
4455
+ });
4456
+ }
4457
+ dispatch({ type: "status", status: "idle" });
4458
+ await new Promise((r) => setTimeout(r, 200));
4459
+ }
4460
+ } finally {
4461
+ parallelLoopRunningRef.current = false;
4462
+ if (getAutonomy) {
4463
+ const finalMode = getAutonomy();
4464
+ if (finalMode !== autonomyLive) setAutonomyLive(finalMode);
4465
+ }
4466
+ }
4467
+ };
4468
+ const parallelLoopRunningRef = useRef(false);
4469
+ const runParallelLoopRef = useRef(runParallelLoop);
4470
+ runParallelLoopRef.current = runParallelLoop;
4272
4471
  useEffect(() => {
4273
4472
  if (!subscribeEternalIteration) return;
4274
4473
  const unsub = subscribeEternalIteration((entry) => {
@@ -4280,6 +4479,13 @@ function App({
4280
4479
  });
4281
4480
  return unsub;
4282
4481
  }, [subscribeEternalIteration]);
4482
+ useEffect(() => {
4483
+ if (!subscribeEternalStage) return;
4484
+ const unsub = subscribeEternalStage((stage) => {
4485
+ dispatch({ type: "eternalStage", stage });
4486
+ });
4487
+ return unsub;
4488
+ }, [subscribeEternalStage]);
4283
4489
  const submit = async (overrideRaw) => {
4284
4490
  const raw = overrideRaw ?? draftRef.current.buffer;
4285
4491
  const trimmed = raw.trim();
@@ -4317,6 +4523,9 @@ function App({
4317
4523
  if (currentAutonomy === "eternal" && getEternalEngine) {
4318
4524
  void runEternalLoopRef.current();
4319
4525
  }
4526
+ if (currentAutonomy === "eternal-parallel" && getParallelEngine) {
4527
+ void runParallelLoopRef.current();
4528
+ }
4320
4529
  }
4321
4530
  if (res?.exit) {
4322
4531
  exit();
@@ -4462,6 +4671,14 @@ User message:
4462
4671
  hint: state.modelPicker.hint
4463
4672
  }
4464
4673
  ) : null,
4674
+ state.autonomyPicker.open ? /* @__PURE__ */ jsx(
4675
+ AutonomyPicker,
4676
+ {
4677
+ options: state.autonomyPicker.options,
4678
+ selected: state.autonomyPicker.selected,
4679
+ hint: state.autonomyPicker.hint
4680
+ }
4681
+ ) : null,
4465
4682
  state.rewindOverlay ? /* @__PURE__ */ jsx(
4466
4683
  CheckpointTimeline,
4467
4684
  {
@@ -4510,7 +4727,8 @@ User message:
4510
4727
  projectName,
4511
4728
  subagentCount: Object.keys(state.fleet).length,
4512
4729
  processCount: getProcessRegistry().activeCount,
4513
- hiddenItems
4730
+ hiddenItems,
4731
+ eternalStage: state.eternalStage
4514
4732
  }
4515
4733
  ),
4516
4734
  director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
@@ -4639,13 +4857,16 @@ async function runTui(opts) {
4639
4857
  getYolo: opts.getYolo,
4640
4858
  getAutonomy: opts.getAutonomy,
4641
4859
  getEternalEngine: opts.getEternalEngine,
4860
+ getParallelEngine: opts.getParallelEngine,
4642
4861
  subscribeEternalIteration: opts.subscribeEternalIteration,
4862
+ subscribeEternalStage: opts.subscribeEternalStage,
4643
4863
  appVersion: opts.appVersion,
4644
4864
  provider: opts.provider,
4645
4865
  family: opts.family,
4646
4866
  keyTail: opts.keyTail,
4647
4867
  getPickableProviders: opts.getPickableProviders,
4648
4868
  switchProviderAndModel: opts.switchProviderAndModel,
4869
+ switchAutonomy: opts.switchAutonomy,
4649
4870
  effectiveMaxContext: opts.effectiveMaxContext,
4650
4871
  onExit,
4651
4872
  director: opts.director ?? null,