@wrongstack/tui 0.41.0 → 0.51.3

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
@@ -13,6 +13,54 @@ interface ProviderOption {
13
13
  modelsLabel?: string;
14
14
  }
15
15
 
16
+ type SerialAutonomyStage = {
17
+ phase: 'idle';
18
+ } | {
19
+ phase: 'decide';
20
+ reason: string;
21
+ } | {
22
+ phase: 'execute';
23
+ task: string;
24
+ } | {
25
+ phase: 'reflect';
26
+ status: 'success' | 'failure' | 'aborted' | 'skipped';
27
+ note?: string;
28
+ } | {
29
+ phase: 'sleep';
30
+ ms: number;
31
+ } | {
32
+ phase: 'paused';
33
+ } | {
34
+ phase: 'stopped';
35
+ } | {
36
+ phase: 'error';
37
+ message: string;
38
+ };
39
+ type ParallelAutonomyStage = {
40
+ phase: 'idle';
41
+ } | {
42
+ phase: 'decompose';
43
+ } | {
44
+ phase: 'fanout';
45
+ slots: number;
46
+ } | {
47
+ phase: 'await';
48
+ taskIds: string[];
49
+ } | {
50
+ phase: 'aggregate';
51
+ successCount: number;
52
+ total: number;
53
+ goalComplete: boolean;
54
+ } | {
55
+ phase: 'sleep';
56
+ ms: number;
57
+ } | {
58
+ phase: 'stopped';
59
+ } | {
60
+ phase: 'error';
61
+ message: string;
62
+ };
63
+ type AutonomyStage = SerialAutonomyStage | ParallelAutonomyStage;
16
64
  interface RunTuiOptions {
17
65
  agent: Agent;
18
66
  slashRegistry: SlashCommandRegistry;
@@ -51,33 +99,10 @@ interface RunTuiOptions {
51
99
  */
52
100
  subscribeEternalIteration?: (fn: (entry: _wrongstack_core.JournalEntry) => void) => () => void;
53
101
  /**
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.
102
+ * Subscribe to per-iteration stage transitions from the autonomy engines.
103
+ * TUI uses this to render live status in the status bar.
57
104
  */
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;
105
+ subscribeEternalStage?: (fn: (stage: AutonomyStage) => void) => () => void;
81
106
  /** Renders in the startup banner. Read from the CLI's package.json. */
82
107
  appVersion?: string;
83
108
  /** Provider id for the startup banner ("openai", "anthropic", ...). */
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ function StatusBar({
54
54
  git,
55
55
  subagentCount = 0,
56
56
  context,
57
+ brain,
57
58
  projectName,
58
59
  processCount,
59
60
  hiddenItems,
@@ -79,7 +80,8 @@ function StatusBar({
79
80
  const { label: stateLabel, color: stateColor } = stateChip(state, fleet?.running ?? 0);
80
81
  const hasSecondLine = yolo || autonomy && autonomy !== "off" || elapsedMs !== void 0 || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0 || goalSummary !== null && goalSummary !== void 0;
81
82
  const fleetHasActivity = fleet && (fleet.running > 0 || fleet.idle > 0 || fleet.pending > 0 || fleet.completed > 0) || subagentCount > 0;
82
- const hasThirdLine = todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) || fleetHasActivity;
83
+ const hasBrainActivity = !!brain && brain.state !== "idle";
84
+ const hasThirdLine = todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) || fleetHasActivity || hasBrainActivity;
83
85
  return /* @__PURE__ */ jsxs(
84
86
  Box,
85
87
  {
@@ -316,6 +318,10 @@ function StatusBar({
316
318
  " agent",
317
319
  subagentCount === 1 ? "" : "s"
318
320
  ] })
321
+ ] }) : null,
322
+ hasBrainActivity && brain ? /* @__PURE__ */ jsxs(Fragment, { children: [
323
+ todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) || fleetHasActivity ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
324
+ /* @__PURE__ */ jsx(BrainChip, { brain })
319
325
  ] }) : null
320
326
  ] }) : null,
321
327
  fleetAgents && fleetAgents.length > 0 ? /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, children: fleetAgents.map((a, i) => (
@@ -348,6 +354,18 @@ function StatusBar({
348
354
  }
349
355
  );
350
356
  }
357
+ function BrainChip({ brain }) {
358
+ const color = brain.state === "denied" ? "red" : brain.state === "ask_human" ? "yellow" : brain.state === "deciding" ? "magenta" : "cyan";
359
+ const label = brain.state === "deciding" ? "deciding" : brain.state === "ask_human" ? "human" : brain.state;
360
+ const scope = brain.source ? ` ${brain.source}` : "";
361
+ const summary = brain.summary ? ` \xB7 ${brain.summary.slice(0, 40)}` : "";
362
+ return /* @__PURE__ */ jsxs(Text, { color, children: [
363
+ "\u{1F9E0} ",
364
+ label,
365
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: scope }),
366
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: summary })
367
+ ] });
368
+ }
351
369
  function EternalStageChip({
352
370
  stage
353
371
  }) {
@@ -376,6 +394,25 @@ function EternalStageChip({
376
394
  ]
377
395
  }
378
396
  );
397
+ case "decompose":
398
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u2B07 decompose" });
399
+ case "fanout":
400
+ return /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
401
+ "\u21C4 fanout: ",
402
+ stage.slots
403
+ ] });
404
+ case "await":
405
+ return /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
406
+ "\u23F3 await: ",
407
+ stage.taskIds.length
408
+ ] });
409
+ case "aggregate":
410
+ return /* @__PURE__ */ jsxs(Text, { color: stage.goalComplete ? "green" : "magenta", children: [
411
+ "\u21A9 aggregate: ",
412
+ stage.successCount,
413
+ "/",
414
+ stage.total
415
+ ] });
379
416
  case "sleep":
380
417
  return /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
381
418
  "\u{1F4A4} sleep ",
@@ -734,6 +771,9 @@ function fmtTokens2(n) {
734
771
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
735
772
  return `${(n / 1e6).toFixed(1)}M`;
736
773
  }
774
+ function fmtExactTokens(n) {
775
+ return `${Math.round(n).toLocaleString("en-US")} tok`;
776
+ }
737
777
  function snippet(s2, max = 72) {
738
778
  const oneLine2 = s2.replace(/\s+/g, " ").trim();
739
779
  if (oneLine2.length <= max) return oneLine2;
@@ -831,6 +871,10 @@ function AgentsMonitor({
831
871
  /* @__PURE__ */ jsx(Text, { color: s2.color, bold: true, children: s2.icon }),
832
872
  /* @__PURE__ */ jsx(Text, { bold: true, children: e.name }),
833
873
  fmtModelLabel(e.provider, e.model) ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: fmtModelLabel(e.provider, e.model) }) : null,
874
+ e.ctxMaxTokens && e.ctxMaxTokens > 0 ? /* @__PURE__ */ jsxs(Text, { color: "blue", children: [
875
+ "ctx max ",
876
+ fmtExactTokens(e.ctxMaxTokens)
877
+ ] }) : null,
834
878
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
835
879
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed }),
836
880
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
@@ -952,6 +996,92 @@ function AutonomyPicker({
952
996
  hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
953
997
  ] });
954
998
  }
999
+ function riskColor(risk) {
1000
+ switch (risk) {
1001
+ case "low":
1002
+ return "green";
1003
+ case "medium":
1004
+ return "cyan";
1005
+ case "high":
1006
+ return "yellow";
1007
+ case "critical":
1008
+ return "red";
1009
+ default:
1010
+ return "white";
1011
+ }
1012
+ }
1013
+ function optionKey(index) {
1014
+ return String.fromCharCode("A".charCodeAt(0) + index);
1015
+ }
1016
+ function contextLines(context) {
1017
+ if (!context?.trim()) return [];
1018
+ return context.trim().split("\n").slice(0, 5);
1019
+ }
1020
+ function BrainDecisionPrompt({
1021
+ requestId,
1022
+ source,
1023
+ risk,
1024
+ question,
1025
+ context,
1026
+ options = [],
1027
+ onAnswer
1028
+ }) {
1029
+ const color = riskColor(risk);
1030
+ const ctx = contextLines(context);
1031
+ useInput((input, key) => {
1032
+ if (!onAnswer) return;
1033
+ if (key.escape || input.toLowerCase() === "d") {
1034
+ onAnswer({ id: requestId, deny: true, text: "Denied by human from TUI." });
1035
+ return;
1036
+ }
1037
+ const ch = input.toLowerCase();
1038
+ const index = ch >= "a" && ch <= "z" ? ch.charCodeAt(0) - "a".charCodeAt(0) : Number(ch) - 1;
1039
+ const option = Number.isInteger(index) && index >= 0 ? options[index] : void 0;
1040
+ if (option) onAnswer({ id: requestId, optionId: option.id, text: option.label });
1041
+ });
1042
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
1043
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "\u{1F9E0} BRAIN REQUIRES HUMAN DECISION" }) }),
1044
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1045
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Source:" }),
1046
+ /* @__PURE__ */ jsx(Text, { children: source }),
1047
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Risk:" }),
1048
+ /* @__PURE__ */ jsx(Text, { color, children: risk })
1049
+ ] }),
1050
+ /* @__PURE__ */ jsx(Text, { color: "white", children: question }),
1051
+ ctx.length > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1052
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Context:" }),
1053
+ ctx.map((line, index) => (
1054
+ // biome-ignore lint/suspicious/noArrayIndexKey: context lines are static for this prompt render
1055
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: line }, index)
1056
+ ))
1057
+ ] }) : null,
1058
+ options.length > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1059
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Options:" }),
1060
+ options.slice(0, 6).map((option, index) => {
1061
+ const key = optionKey(index);
1062
+ const optionColor = riskColor(String(option.risk ?? risk));
1063
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1064
+ /* @__PURE__ */ jsxs(Text, { children: [
1065
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", children: [
1066
+ "[",
1067
+ key,
1068
+ "]"
1069
+ ] }),
1070
+ " ",
1071
+ /* @__PURE__ */ jsx(Text, { color: optionColor, children: option.label }),
1072
+ option.recommended ? /* @__PURE__ */ jsx(Text, { color: "green", children: " recommended" }) : null
1073
+ ] }),
1074
+ option.consequence ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1075
+ " ",
1076
+ option.consequence
1077
+ ] }) : null
1078
+ ] }, option.id);
1079
+ })
1080
+ ] }) : null,
1081
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
1082
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press A/B/C or 1/2/3 to answer; Esc or D denies with the safe default." })
1083
+ ] });
1084
+ }
955
1085
  function CheckpointTimeline({
956
1086
  checkpoints,
957
1087
  selected,
@@ -2318,6 +2448,30 @@ function DiffBlock({ rows, hidden }) {
2318
2448
  hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `${blank} \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
2319
2449
  ] });
2320
2450
  }
2451
+ function brainStatusStyle(status) {
2452
+ switch (status) {
2453
+ case "thinking":
2454
+ return { icon: "\u2026", color: "magenta" };
2455
+ case "answered":
2456
+ return { icon: "\u2696", color: "cyan" };
2457
+ case "ask_human":
2458
+ return { icon: "?", color: "yellow" };
2459
+ case "denied":
2460
+ return { icon: "\xD7", color: "red" };
2461
+ }
2462
+ }
2463
+ function brainRiskColor(risk) {
2464
+ switch (risk) {
2465
+ case "low":
2466
+ return "green";
2467
+ case "medium":
2468
+ return "cyan";
2469
+ case "high":
2470
+ return "yellow";
2471
+ case "critical":
2472
+ return "red";
2473
+ }
2474
+ }
2321
2475
  var Entry = React4.memo(function Entry2({
2322
2476
  entry,
2323
2477
  termWidth
@@ -2450,6 +2604,38 @@ var Entry = React4.memo(function Entry2({
2450
2604
  );
2451
2605
  case "turn-summary":
2452
2606
  return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
2607
+ case "brain": {
2608
+ const statusStyle = brainStatusStyle(entry.status);
2609
+ const riskColor2 = brainRiskColor(entry.risk);
2610
+ return /* @__PURE__ */ jsxs(
2611
+ Box,
2612
+ {
2613
+ flexDirection: "column",
2614
+ marginY: 1,
2615
+ borderStyle: "single",
2616
+ borderTop: false,
2617
+ borderRight: false,
2618
+ borderBottom: false,
2619
+ borderColor: "magenta",
2620
+ paddingLeft: 1,
2621
+ children: [
2622
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2623
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "BRAIN" }),
2624
+ /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: statusStyle.icon }),
2625
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.source }),
2626
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
2627
+ /* @__PURE__ */ jsx(Text, { color: riskColor2, children: entry.risk })
2628
+ ] }),
2629
+ /* @__PURE__ */ jsx(Text, { color: "white", children: entry.question }),
2630
+ entry.decision ? /* @__PURE__ */ jsxs(Text, { children: [
2631
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Decision: " }),
2632
+ /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: entry.decision })
2633
+ ] }) : null,
2634
+ entry.rationale ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.rationale }) : null
2635
+ ]
2636
+ }
2637
+ );
2638
+ }
2453
2639
  case "confirm":
2454
2640
  return /* @__PURE__ */ jsxs(
2455
2641
  Box,
@@ -4574,6 +4760,21 @@ function reducer(state, action) {
4574
4760
  return { ...state, interrupts: 0 };
4575
4761
  case "hint":
4576
4762
  return { ...state, hint: action.text };
4763
+ case "brainStatus":
4764
+ return {
4765
+ ...state,
4766
+ brain: {
4767
+ state: action.state,
4768
+ source: action.source,
4769
+ risk: action.risk,
4770
+ summary: action.summary,
4771
+ updatedAt: Date.now()
4772
+ }
4773
+ };
4774
+ case "brainPromptSet":
4775
+ return { ...state, brainPrompt: action.prompt };
4776
+ case "brainPromptClear":
4777
+ return { ...state, brainPrompt: null };
4577
4778
  case "pickerOpen":
4578
4779
  return {
4579
4780
  ...state,
@@ -5534,6 +5735,7 @@ function App({
5534
5735
  const { exit } = useApp();
5535
5736
  const [liveModel, setLiveModel] = useState(model);
5536
5737
  const [liveProvider, setLiveProvider] = useState(provider ?? "agent");
5738
+ const [activeMaxContext, setActiveMaxContext] = useState(effectiveMaxContext);
5537
5739
  const [yoloLive, setYoloLive] = useState(yolo);
5538
5740
  const [autonomyLive, setAutonomyLive] = useState(getAutonomy?.() ?? "off");
5539
5741
  const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
@@ -5602,6 +5804,8 @@ function App({
5602
5804
  steeringPending: false,
5603
5805
  steerSnapshot: null,
5604
5806
  hint: "",
5807
+ brain: { state: "idle" },
5808
+ brainPrompt: null,
5605
5809
  nextId: 1,
5606
5810
  picker: { open: false, query: "", matches: [], selected: 0 },
5607
5811
  slashPicker: { open: false, query: "", matches: [], selected: 0 },
@@ -5943,7 +6147,7 @@ function App({
5943
6147
  clearInterval(t);
5944
6148
  };
5945
6149
  }, [agent.ctx.cwd]);
5946
- const maxContext = effectiveMaxContext ?? agent.ctx.provider.capabilities.maxContext;
6150
+ const maxContext = activeMaxContext ?? agent.ctx.provider.capabilities.maxContext;
5947
6151
  const currentContextTokens = (tokenCounter?.currentRequestTokens()?.input ?? 0) + (tokenCounter?.currentRequestTokens()?.cacheRead ?? 0);
5948
6152
  const contextWindow = useMemo(() => {
5949
6153
  void state.contextChipVersion;
@@ -5989,10 +6193,10 @@ function App({
5989
6193
  currentTool: state.leader.currentTool,
5990
6194
  ctxPct: state.leader.ctxPct,
5991
6195
  ctxTokens: state.leader.ctxTokens,
5992
- ctxMaxTokens: state.leader.ctxMaxTokens
6196
+ ctxMaxTokens: state.leader.ctxMaxTokens ?? effectiveMaxContext
5993
6197
  };
5994
6198
  return { leader: leaderEntry, ...state.fleet };
5995
- }, [state.fleet, state.leader, state.status, provider, model]);
6199
+ }, [state.fleet, state.leader, state.status, provider, model, effectiveMaxContext]);
5996
6200
  const STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
5997
6201
  const labelsRef = useRef(/* @__PURE__ */ new Map());
5998
6202
  const labelFor = (id, name) => {
@@ -6769,6 +6973,7 @@ function App({
6769
6973
  });
6770
6974
  });
6771
6975
  const offLeaderCtxPct = events.on("ctx.pct", (e) => {
6976
+ setActiveMaxContext(e.maxContext);
6772
6977
  dispatch({
6773
6978
  type: "leaderCtxPct",
6774
6979
  load: e.load,
@@ -6776,6 +6981,9 @@ function App({
6776
6981
  maxContext: e.maxContext
6777
6982
  });
6778
6983
  });
6984
+ const offLeaderMaxContext = events.on("ctx.max_context", (e) => {
6985
+ if (e.maxContext > 0) setActiveMaxContext(e.maxContext);
6986
+ });
6779
6987
  const offTool = events.on("subagent.tool_executed", (e) => {
6780
6988
  dispatch({
6781
6989
  type: "fleetTool",
@@ -6796,6 +7004,7 @@ function App({
6796
7004
  offIterationSummary();
6797
7005
  offCtxPct();
6798
7006
  offLeaderCtxPct();
7007
+ offLeaderMaxContext();
6799
7008
  offTool();
6800
7009
  };
6801
7010
  }, [events, director]);
@@ -6823,6 +7032,71 @@ function App({
6823
7032
  offRewound();
6824
7033
  };
6825
7034
  }, [events, onClearHistory]);
7035
+ useEffect(() => {
7036
+ const requestSummary = (request) => `${request.source}: ${request.question}`.slice(0, 80);
7037
+ const addBrainEntry = (status, payload) => {
7038
+ const p = payload;
7039
+ const decision = p.decision.optionId ?? p.decision.text ?? p.decision.reason ?? p.decision.prompt ?? p.decision.type;
7040
+ dispatch({
7041
+ type: "brainStatus",
7042
+ state: status,
7043
+ source: p.request.source,
7044
+ risk: p.request.risk,
7045
+ summary: decision
7046
+ });
7047
+ if (status === "ask_human") {
7048
+ dispatch({
7049
+ type: "brainPromptSet",
7050
+ prompt: {
7051
+ requestId: p.request.id,
7052
+ source: p.request.source,
7053
+ risk: p.request.risk,
7054
+ question: p.request.question,
7055
+ context: p.request.context,
7056
+ options: p.request.options
7057
+ }
7058
+ });
7059
+ } else {
7060
+ dispatch({ type: "brainPromptClear" });
7061
+ }
7062
+ dispatch({
7063
+ type: "addEntry",
7064
+ entry: {
7065
+ kind: "brain",
7066
+ status,
7067
+ source: p.request.source,
7068
+ risk: p.request.risk,
7069
+ question: p.request.question,
7070
+ decision,
7071
+ rationale: p.decision.rationale
7072
+ }
7073
+ });
7074
+ };
7075
+ const offRequested = events.on("brain.decision_requested", ({ request }) => {
7076
+ dispatch({
7077
+ type: "brainStatus",
7078
+ state: "deciding",
7079
+ source: request.source,
7080
+ risk: request.risk,
7081
+ summary: requestSummary(request)
7082
+ });
7083
+ });
7084
+ const offAnswered = events.on("brain.decision_answered", (payload) => {
7085
+ addBrainEntry("answered", payload);
7086
+ });
7087
+ const offAskHuman = events.on("brain.decision_ask_human", (payload) => {
7088
+ addBrainEntry("ask_human", payload);
7089
+ });
7090
+ const offDenied = events.on("brain.decision_denied", (payload) => {
7091
+ addBrainEntry("denied", payload);
7092
+ });
7093
+ return () => {
7094
+ offRequested();
7095
+ offAnswered();
7096
+ offAskHuman();
7097
+ offDenied();
7098
+ };
7099
+ }, [events]);
6826
7100
  useEffect(() => {
6827
7101
  if (!subscribeAutoPhase) return;
6828
7102
  const handler = (event, payload) => {
@@ -7613,6 +7887,7 @@ function App({
7613
7887
  }
7614
7888
  setLiveProvider(providerId);
7615
7889
  setLiveModel(modelId);
7890
+ setActiveMaxContext(agent.ctx.provider.capabilities.maxContext);
7616
7891
  dispatch({
7617
7892
  type: "addEntry",
7618
7893
  entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
@@ -8213,6 +8488,10 @@ function App({
8213
8488
  if (ctxModel && ctxModel !== liveModel) setLiveModel(ctxModel);
8214
8489
  const ctxProviderId = agent.ctx.provider?.id;
8215
8490
  if (ctxProviderId && ctxProviderId !== liveProvider) setLiveProvider(ctxProviderId);
8491
+ const ctxMaxContext = agent.ctx.provider.capabilities.maxContext;
8492
+ if (ctxMaxContext > 0 && ctxMaxContext !== activeMaxContext) {
8493
+ setActiveMaxContext(ctxMaxContext);
8494
+ }
8216
8495
  if (getYolo) {
8217
8496
  const currentYolo = getYolo();
8218
8497
  if (currentYolo !== yoloLive) setYoloLive(currentYolo);
@@ -8437,6 +8716,16 @@ User message:
8437
8716
  onClose: () => dispatch({ type: "rewindOverlayClose" })
8438
8717
  }
8439
8718
  ) : null,
8719
+ state.brainPrompt ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, flexShrink: 0, children: /* @__PURE__ */ jsx(
8720
+ BrainDecisionPrompt,
8721
+ {
8722
+ ...state.brainPrompt,
8723
+ onAnswer: (answer) => {
8724
+ events.emit("brain.human_answered", { ...answer, at: Date.now() });
8725
+ dispatch({ type: "brainPromptClear" });
8726
+ }
8727
+ }
8728
+ ) }) : null,
8440
8729
  state.confirmQueue.length > 0 && (() => {
8441
8730
  const head = state.confirmQueue[0];
8442
8731
  let resolved = false;
@@ -8474,6 +8763,7 @@ User message:
8474
8763
  fleet: fleetCounts,
8475
8764
  git: gitInfo,
8476
8765
  context: contextWindow,
8766
+ brain: state.brain,
8477
8767
  projectName,
8478
8768
  subagentCount: Object.keys(state.fleet).length,
8479
8769
  processCount: getProcessRegistry().activeCount,