reasonix 0.30.1 → 0.30.2

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/cli/index.js CHANGED
@@ -20890,7 +20890,8 @@ var PAGE_MARGIN = 6;
20890
20890
  function SessionPicker({
20891
20891
  sessions: sessions2,
20892
20892
  workspace: workspace2,
20893
- onChoose
20893
+ onChoose,
20894
+ walletCurrency
20894
20895
  }) {
20895
20896
  const [focus, setFocus] = useState11(0);
20896
20897
  const [renaming, setRenaming] = useState11(null);
@@ -20947,18 +20948,28 @@ function SessionPicker({
20947
20948
  const end = Math.min(sessions2.length, start + visibleCount);
20948
20949
  const shown = sessions2.slice(start, end);
20949
20950
  const hiddenBelow = sessions2.length - end;
20950
- return /* @__PURE__ */ React28.createElement(Box21, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { bold: true, color: TONE.brand }, " \u25C8 REASONIX \xB7 pick a session "), /* @__PURE__ */ React28.createElement(Text20, { color: FG.meta }, ` \xB7 ${workspace2}`)), /* @__PURE__ */ React28.createElement(Box21, { height: 1 }), sessions2.length === 0 ? /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, " no saved sessions in this workspace yet \u2014 press "), /* @__PURE__ */ React28.createElement(Text20, { bold: true, color: TONE.brand }, "\u23CE"), /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, " to start a new one")) : shown.map((s, i) => /* @__PURE__ */ React28.createElement(SessionRow, { key: s.name, info: s, focused: start + i === focus })), hiddenBelow > 0 ? /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, ` \u2026 ${hiddenBelow} more`)) : null, renaming ? /* @__PURE__ */ React28.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, ` rename "${renaming.from}" \u2192 `), /* @__PURE__ */ React28.createElement(Text20, { bold: true, color: TONE.brand }, renaming.buf), /* @__PURE__ */ React28.createElement(Text20, { backgroundColor: TONE.brand, color: "black" }, " ")) : null, /* @__PURE__ */ React28.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, renaming ? " \u23CE confirm rename \xB7 esc cancel" : sessions2.length === 0 ? " \u23CE new session \xB7 esc quit" : " \u2191\u2193 pick \xB7 \u23CE open \xB7 [n] new \xB7 [d] delete \xB7 [r] rename \xB7 esc quit")));
20951
+ return /* @__PURE__ */ React28.createElement(Box21, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { bold: true, color: TONE.brand }, " \u25C8 REASONIX \xB7 pick a session "), /* @__PURE__ */ React28.createElement(Text20, { color: FG.meta }, ` \xB7 ${workspace2}`)), /* @__PURE__ */ React28.createElement(Box21, { height: 1 }), sessions2.length === 0 ? /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, " no saved sessions in this workspace yet \u2014 press "), /* @__PURE__ */ React28.createElement(Text20, { bold: true, color: TONE.brand }, "\u23CE"), /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, " to start a new one")) : shown.map((s, i) => /* @__PURE__ */ React28.createElement(
20952
+ SessionRow,
20953
+ {
20954
+ key: s.name,
20955
+ info: s,
20956
+ focused: start + i === focus,
20957
+ walletCurrency
20958
+ }
20959
+ )), hiddenBelow > 0 ? /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, ` \u2026 ${hiddenBelow} more`)) : null, renaming ? /* @__PURE__ */ React28.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, ` rename "${renaming.from}" \u2192 `), /* @__PURE__ */ React28.createElement(Text20, { bold: true, color: TONE.brand }, renaming.buf), /* @__PURE__ */ React28.createElement(Text20, { backgroundColor: TONE.brand, color: "black" }, " ")) : null, /* @__PURE__ */ React28.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, renaming ? " \u23CE confirm rename \xB7 esc cancel" : sessions2.length === 0 ? " \u23CE new session \xB7 esc quit" : " \u2191\u2193 pick \xB7 \u23CE open \xB7 [n] new \xB7 [d] delete \xB7 [r] rename \xB7 esc quit")));
20951
20960
  }
20952
20961
  function SessionRow({
20953
20962
  info,
20954
- focused
20963
+ focused,
20964
+ walletCurrency
20955
20965
  }) {
20956
20966
  const branch2 = info.meta.branch ?? "main";
20957
20967
  const summary = info.meta.summary ?? `${info.messageCount} message${info.messageCount === 1 ? "" : "s"}`;
20958
20968
  const turns = info.meta.turnCount ?? Math.ceil(info.messageCount / 2);
20959
- const costCny = info.meta.totalCostUsd !== void 0 ? `\xA5${(info.meta.totalCostUsd * USD_TO_CNY).toFixed(2)}` : "";
20969
+ const currency = walletCurrency ?? info.meta.balanceCurrency;
20970
+ const costLabel = info.meta.totalCostUsd !== void 0 ? formatCost(info.meta.totalCostUsd, currency, 2) : "";
20960
20971
  const time = relativeTime2(info.mtime);
20961
- return /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { color: focused ? TONE.brand : FG.faint }, focused ? " \u25B8 " : " "), /* @__PURE__ */ React28.createElement(Text20, { bold: focused, color: focused ? FG.strong : FG.sub }, info.name.padEnd(12)), /* @__PURE__ */ React28.createElement(Text20, { color: FG.meta }, ` \xB7 ${branch2.padEnd(8)} \xB7 `), /* @__PURE__ */ React28.createElement(Text20, { color: focused ? FG.body : FG.sub }, truncate2(summary, 40)), /* @__PURE__ */ React28.createElement(Box21, { flexGrow: 1 }), /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, `${time.padStart(11)} `), /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, `${turns} turns`), costCny ? /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, ` \xB7 ${costCny}`) : null);
20972
+ return /* @__PURE__ */ React28.createElement(Box21, null, /* @__PURE__ */ React28.createElement(Text20, { color: focused ? TONE.brand : FG.faint }, focused ? " \u25B8 " : " "), /* @__PURE__ */ React28.createElement(Text20, { bold: focused, color: focused ? FG.strong : FG.sub }, info.name.padEnd(12)), /* @__PURE__ */ React28.createElement(Text20, { color: FG.meta }, ` \xB7 ${branch2.padEnd(8)} \xB7 `), /* @__PURE__ */ React28.createElement(Text20, { color: focused ? FG.body : FG.sub }, truncate2(summary, 40)), /* @__PURE__ */ React28.createElement(Box21, { flexGrow: 1 }), /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, `${time.padStart(11)} `), /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, `${turns} turns`), costLabel ? /* @__PURE__ */ React28.createElement(Text20, { color: FG.faint }, ` \xB7 ${costLabel}`) : null);
20962
20973
  }
20963
20974
  function truncate2(s, max) {
20964
20975
  if (s.length <= max) return s;
@@ -22953,7 +22964,11 @@ function groupByFile(hits) {
22953
22964
 
22954
22965
  // src/cli/ui/cards/StreamingCard.tsx
22955
22966
  import { Box as Box39, Text as Text40, useStdout as useStdout9 } from "ink";
22956
- import React50 from "react";
22967
+ import React50, { useContext as useContext10 } from "react";
22968
+
22969
+ // src/cli/ui/layout/LiveExpandContext.ts
22970
+ import { createContext as createContext9 } from "react";
22971
+ var LiveExpandContext = createContext9(false);
22957
22972
 
22958
22973
  // src/cli/ui/markdown.tsx
22959
22974
  import { highlight, supportsLanguage } from "cli-highlight";
@@ -23268,40 +23283,81 @@ function plainText(tokens) {
23268
23283
 
23269
23284
  // src/cli/ui/cards/StreamingCard.tsx
23270
23285
  var STREAMING_PREVIEW_LINES2 = 4;
23286
+ var EXPANDED_MAX_LINES = 60;
23287
+ var MIN_ELAPSED_MS_FOR_RATE = 500;
23288
+ var MIN_TOKENS_FOR_RATE = 4;
23289
+ function formatTokenCount(n) {
23290
+ if (n >= 1e4) return `${(n / 1e3).toFixed(1)}k`;
23291
+ if (n >= 1e3) return `${(n / 1e3).toFixed(2)}k`;
23292
+ return String(n);
23293
+ }
23294
+ function tokenRate(text, startTs, endTs) {
23295
+ const tokens = countTokens(text);
23296
+ const elapsedMs = endTs - startTs;
23297
+ if (elapsedMs < MIN_ELAPSED_MS_FOR_RATE || tokens < MIN_TOKENS_FOR_RATE) {
23298
+ return { tokens, tps: null };
23299
+ }
23300
+ return { tokens, tps: Math.round(tokens * 1e3 / elapsedMs) };
23301
+ }
23302
+ var PILL_RATE = { bg: "#11141a", fg: "#8b949e" };
23271
23303
  function StreamingCard({ card }) {
23272
23304
  const { stdout: stdout4 } = useStdout9();
23273
23305
  const cols = stdout4?.columns ?? 80;
23306
+ const expanded = useContext10(LiveExpandContext);
23307
+ const reserveCap = expanded ? EXPANDED_MAX_LINES + 2 : STREAMING_PREVIEW_LINES2 + 2;
23274
23308
  useReserveRows("stream", {
23275
23309
  min: STREAMING_PREVIEW_LINES2 + 1,
23276
- max: STREAMING_PREVIEW_LINES2 + 2
23310
+ max: reserveCap
23277
23311
  });
23312
+ useSlowTick();
23278
23313
  const modelBadge = card.model ? modelBadgeFor(card.model) : null;
23279
23314
  const modelPill = modelBadge ? /* @__PURE__ */ React50.createElement(Pill, { label: modelBadge.label, ...PILL_MODEL[modelBadge.kind], bold: false }) : null;
23280
23315
  if (card.done && !card.aborted) {
23281
- return /* @__PURE__ */ React50.createElement(Card, { tone: TONE.ok }, /* @__PURE__ */ React50.createElement(CardHeader, { glyph: "\u2039", tone: TONE.ok, title: "reply", right: modelPill }), /* @__PURE__ */ React50.createElement(Markdown, { text: card.text }));
23316
+ const { tokens, tps } = tokenRate(card.text, card.ts, card.endedAt ?? Date.now());
23317
+ const ratePill = tokens >= MIN_TOKENS_FOR_RATE && tps !== null ? /* @__PURE__ */ React50.createElement(Pill, { label: `${formatTokenCount(tokens)} tok \xB7 ${tps} t/s`, ...PILL_RATE, bold: false }) : null;
23318
+ return /* @__PURE__ */ React50.createElement(Card, { tone: TONE.ok }, /* @__PURE__ */ React50.createElement(
23319
+ CardHeader,
23320
+ {
23321
+ glyph: "\u2039",
23322
+ tone: TONE.ok,
23323
+ title: "reply",
23324
+ right: /* @__PURE__ */ React50.createElement(React50.Fragment, null, ratePill, modelPill)
23325
+ }
23326
+ ), /* @__PURE__ */ React50.createElement(Markdown, { text: card.text }));
23282
23327
  }
23283
23328
  const lineCells = Math.max(20, cols - 4);
23284
23329
  const allLines = card.text.length > 0 ? card.text.split("\n") : [""];
23285
23330
  const visualLines = allLines.flatMap((l) => wrapToCells(l, lineCells));
23286
- const visible = visualLines.slice(-STREAMING_PREVIEW_LINES2);
23331
+ const cap = expanded ? EXPANDED_MAX_LINES : STREAMING_PREVIEW_LINES2;
23332
+ const visible = visualLines.slice(-cap);
23333
+ const droppedAbove = Math.max(0, visualLines.length - visible.length);
23287
23334
  const aborted = !!card.aborted;
23288
23335
  const headColor = aborted ? TONE.err : TONE_ACTIVE.brand;
23289
23336
  const glyph = aborted ? "\u2039" : "\u25C8";
23290
23337
  const headLabel = aborted ? "aborted" : "writing\u2026";
23338
+ const { tokens: liveTokens, tps: liveTps } = tokenRate(card.text, card.ts, Date.now());
23339
+ const liveRatePill = !aborted && liveTokens >= MIN_TOKENS_FOR_RATE && liveTps !== null ? /* @__PURE__ */ React50.createElement(Pill, { label: `${liveTps} t/s`, ...PILL_RATE, bold: false }) : null;
23340
+ const expandPill = !aborted ? /* @__PURE__ */ React50.createElement(Pill, { label: expanded ? "expanded \u2303o" : "preview \u2303o", ...PILL_RATE, bold: false }) : null;
23291
23341
  return /* @__PURE__ */ React50.createElement(Card, { tone: headColor }, /* @__PURE__ */ React50.createElement(
23292
23342
  CardHeader,
23293
23343
  {
23294
23344
  glyph,
23295
23345
  tone: headColor,
23296
23346
  title: headLabel,
23297
- right: /* @__PURE__ */ React50.createElement(React50.Fragment, null, aborted ? null : /* @__PURE__ */ React50.createElement(Spinner, { kind: "braille", color: TONE_ACTIVE.brand }), modelPill)
23347
+ right: /* @__PURE__ */ React50.createElement(React50.Fragment, null, liveRatePill, expandPill, aborted ? null : /* @__PURE__ */ React50.createElement(Spinner, { kind: "braille", color: TONE_ACTIVE.brand }), modelPill)
23298
23348
  }
23299
- ), visible.map((line, i) => /* @__PURE__ */ React50.createElement(Box39, { key: `${card.id}:${allLines.length - visible.length + i}`, flexDirection: "row" }, /* @__PURE__ */ React50.createElement(Text40, { color: aborted ? FG.meta : FG.body }, clipToCells(line, lineCells)))), aborted ? /* @__PURE__ */ React50.createElement(Text40, { color: FG.faint }, "[truncated by esc]") : null);
23349
+ ), expanded && droppedAbove > 0 ? /* @__PURE__ */ React50.createElement(
23350
+ Text40,
23351
+ {
23352
+ color: FG.faint
23353
+ },
23354
+ `\u22EF ${droppedAbove} earlier line${droppedAbove === 1 ? "" : "s"} above`
23355
+ ) : null, visible.map((line, i) => /* @__PURE__ */ React50.createElement(Box39, { key: `${card.id}:${visualLines.length - visible.length + i}`, flexDirection: "row" }, /* @__PURE__ */ React50.createElement(Text40, { color: aborted ? FG.meta : FG.body }, clipToCells(line, lineCells)))), aborted ? /* @__PURE__ */ React50.createElement(Text40, { color: FG.faint }, "[truncated by esc]") : null);
23300
23356
  }
23301
23357
 
23302
23358
  // src/cli/ui/cards/SubAgentCard.tsx
23303
23359
  import { Box as Box40, Text as Text41 } from "ink";
23304
- import React51, { useContext as useContext10 } from "react";
23360
+ import React51, { useContext as useContext11 } from "react";
23305
23361
  var STATUS_COLOR2 = {
23306
23362
  running: TONE_ACTIVE.violet,
23307
23363
  done: TONE.ok,
@@ -23312,7 +23368,7 @@ function SubAgentCard({ card }) {
23312
23368
  const headGlyph = card.status === "failed" ? "\u2716" : "\u232C";
23313
23369
  const runningChildren = card.children.filter((c) => !isChildDone(c)).length;
23314
23370
  const isRunning = card.status === "running";
23315
- const inLive = useContext10(ActiveCardContext);
23371
+ const inLive = useContext11(ActiveCardContext);
23316
23372
  const headerMeta2 = isRunning ? runningChildren > 0 ? [`${runningChildren} running`] : ["working"] : [{ text: card.status, color: headColor }];
23317
23373
  return /* @__PURE__ */ React51.createElement(Card, { tone: headColor }, /* @__PURE__ */ React51.createElement(
23318
23374
  CardHeader,
@@ -23705,14 +23761,15 @@ function isSettled(card) {
23705
23761
  }
23706
23762
  }
23707
23763
  function splitCardStream(cards, suppressLive = false) {
23708
- const lastIdx = cards.length - 1;
23709
- const lastCard = lastIdx >= 0 ? cards[lastIdx] : null;
23710
- const lastIsLive = !!lastCard && !isSettled(lastCard);
23711
- if (suppressLive && lastIsLive) {
23712
- return { committed: cards.slice(0, lastIdx), live: [] };
23713
- }
23714
- const committed = lastIsLive ? cards.slice(0, lastIdx) : cards.slice();
23715
- const live = lastIsLive && lastCard ? [lastCard] : [];
23764
+ const firstUnsettledIdx = cards.findIndex((c) => !isSettled(c));
23765
+ if (firstUnsettledIdx === -1) {
23766
+ return { committed: cards.slice(), live: [] };
23767
+ }
23768
+ const committed = cards.slice(0, firstUnsettledIdx);
23769
+ const live = cards.slice(firstUnsettledIdx);
23770
+ if (suppressLive && live.length > 0 && !isSettled(live[live.length - 1])) {
23771
+ return { committed, live: live.slice(0, -1) };
23772
+ }
23716
23773
  return { committed, live };
23717
23774
  }
23718
23775
  function CardStream({
@@ -23768,14 +23825,15 @@ function UndoBanner({
23768
23825
  }) {
23769
23826
  useTick();
23770
23827
  const totalMs = 5e3;
23771
- const remainingMs = Math.max(0, banner.expiresAt - Date.now());
23828
+ const paused = banner.pausedRemainingMs !== null;
23829
+ const remainingMs = paused ? banner.pausedRemainingMs ?? 0 : Math.max(0, banner.expiresAt - Date.now());
23772
23830
  const remainingSec = Math.ceil(remainingMs / 1e3);
23773
23831
  const ok = banner.results.filter((r) => r.status === "applied" || r.status === "created").length;
23774
23832
  const total = banner.results.length;
23775
- const urgent = remainingSec <= 1;
23833
+ const urgent = !paused && remainingSec <= 1;
23776
23834
  const pct2 = remainingMs / totalMs * 100;
23777
- const tone = urgent ? TONE.err : TONE.accent;
23778
- return /* @__PURE__ */ React59.createElement(Box45, { marginY: 1, paddingX: 1 }, /* @__PURE__ */ React59.createElement(Text47, { backgroundColor: TONE.accent, color: "black", bold: true }, ` \u2713 AUTO-APPLIED ${ok}/${total} `), /* @__PURE__ */ React59.createElement(Text47, { color: FG.faint }, " press "), /* @__PURE__ */ React59.createElement(Text47, { backgroundColor: TONE.brand, color: "black", bold: true }, " u "), /* @__PURE__ */ React59.createElement(Text47, { color: FG.faint }, " to undo "), /* @__PURE__ */ React59.createElement(CharBar, { pct: pct2, width: 20, color: tone, showLabel: false }), /* @__PURE__ */ React59.createElement(Text47, { color: FG.faint }, " "), /* @__PURE__ */ React59.createElement(Text47, { color: tone, bold: urgent }, `${remainingSec}s`));
23835
+ const tone = paused ? TONE.warn : urgent ? TONE.err : TONE.accent;
23836
+ return /* @__PURE__ */ React59.createElement(Box45, { marginY: 1, paddingX: 1 }, /* @__PURE__ */ React59.createElement(Text47, { backgroundColor: TONE.accent, color: "black", bold: true }, ` \u2713 AUTO-APPLIED ${ok}/${total} `), /* @__PURE__ */ React59.createElement(Text47, { color: FG.faint }, " press "), /* @__PURE__ */ React59.createElement(Text47, { backgroundColor: TONE.brand, color: "black", bold: true }, " u "), /* @__PURE__ */ React59.createElement(Text47, { color: FG.faint }, paused ? " to undo \xB7 " : " to undo \xB7 "), /* @__PURE__ */ React59.createElement(Text47, { backgroundColor: paused ? TONE.warn : FG.faint, color: "black", bold: true }, " space "), /* @__PURE__ */ React59.createElement(Text47, { color: FG.faint }, paused ? " to resume " : " to pause "), /* @__PURE__ */ React59.createElement(CharBar, { pct: pct2, width: 20, color: tone, showLabel: false }), /* @__PURE__ */ React59.createElement(Text47, { color: FG.faint }, " "), /* @__PURE__ */ React59.createElement(Text47, { color: tone, bold: urgent || paused }, paused ? `${remainingSec}s \xB7 paused` : `${remainingSec}s`));
23779
23837
  }
23780
23838
  function subagentPhaseLabel(phase, iter, elapsedMs) {
23781
23839
  if (phase === "summarising") return "summarising findings\u2026";
@@ -27417,13 +27475,32 @@ function useEditHistory(codeMode) {
27417
27475
  []
27418
27476
  );
27419
27477
  const armUndoBanner = useCallback8((results) => {
27420
- setUndoBanner({ results, expiresAt: Date.now() + 5e3 });
27478
+ setUndoBanner({ results, expiresAt: Date.now() + 5e3, pausedRemainingMs: null });
27421
27479
  if (undoTimeoutRef.current) clearTimeout(undoTimeoutRef.current);
27422
27480
  undoTimeoutRef.current = setTimeout(() => {
27423
27481
  setUndoBanner(null);
27424
27482
  undoTimeoutRef.current = null;
27425
27483
  }, 5e3);
27426
27484
  }, []);
27485
+ const toggleUndoPause = useCallback8(() => {
27486
+ setUndoBanner((prev) => {
27487
+ if (!prev) return prev;
27488
+ if (prev.pausedRemainingMs === null) {
27489
+ const remaining2 = Math.max(0, prev.expiresAt - Date.now());
27490
+ if (undoTimeoutRef.current) {
27491
+ clearTimeout(undoTimeoutRef.current);
27492
+ undoTimeoutRef.current = null;
27493
+ }
27494
+ return { ...prev, pausedRemainingMs: remaining2 };
27495
+ }
27496
+ const remaining = prev.pausedRemainingMs;
27497
+ undoTimeoutRef.current = setTimeout(() => {
27498
+ setUndoBanner(null);
27499
+ undoTimeoutRef.current = null;
27500
+ }, remaining);
27501
+ return { ...prev, expiresAt: Date.now() + remaining, pausedRemainingMs: null };
27502
+ });
27503
+ }, []);
27427
27504
  const codeUndo = useCallback8(
27428
27505
  (args = []) => {
27429
27506
  if (!codeMode) return "not in code mode";
@@ -27583,6 +27660,7 @@ function useEditHistory(codeMode) {
27583
27660
  undoBanner,
27584
27661
  recordEdit,
27585
27662
  armUndoBanner,
27663
+ toggleUndoPause,
27586
27664
  codeUndo,
27587
27665
  codeHistory,
27588
27666
  codeShowEdit,
@@ -27830,6 +27908,10 @@ function AppInner({
27830
27908
  const isStreaming = useAgentState((s) => s.cards.some((c) => c.kind === "streaming" && !c.done));
27831
27909
  const [input, setInput] = useState18("");
27832
27910
  const [busy, setBusy] = useState18(false);
27911
+ const [liveExpand, setLiveExpand] = useState18(false);
27912
+ useEffect14(() => {
27913
+ if (!isStreaming && liveExpand) setLiveExpand(false);
27914
+ }, [isStreaming, liveExpand]);
27833
27915
  const [languageVersion, setLanguageVersion] = useState18(0);
27834
27916
  useEffect14(() => onLanguageChange(() => setLanguageVersion((v) => v + 1)), []);
27835
27917
  const [liveMcpServers, setLiveMcpServers] = useState18(() => mcpServers ?? []);
@@ -27882,6 +27964,7 @@ function AppInner({
27882
27964
  undoBanner,
27883
27965
  recordEdit,
27884
27966
  armUndoBanner,
27967
+ toggleUndoPause,
27885
27968
  codeUndo,
27886
27969
  codeHistory,
27887
27970
  codeShowEdit,
@@ -28342,7 +28425,7 @@ function AppInner({
28342
28425
  const cur = editModeRef.current;
28343
28426
  const next = cur === "review" ? "auto" : cur === "auto" ? "yolo" : "review";
28344
28427
  setEditMode(next);
28345
- const message = next === "yolo" ? "\u25B8 edit mode: YOLO \u2014 edits AND shell commands auto-run. /undo still rolls back edits. Use carefully." : next === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo. Shell commands still ask." : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)";
28428
+ const message = next === "yolo" ? "\u25B8 edit mode: YOLO \u2014 edits AND shell commands auto-run. /undo still rolls back edits. Use carefully." : next === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo (space pauses the timer). Shell commands still ask." : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)";
28346
28429
  log.pushInfo(message);
28347
28430
  return;
28348
28431
  }
@@ -28354,6 +28437,14 @@ function AppInner({
28354
28437
  log.pushInfo(out);
28355
28438
  return;
28356
28439
  }
28440
+ if (codeMode && input.length === 0 && chKey === " " && undoBanner && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
28441
+ toggleUndoPause();
28442
+ return;
28443
+ }
28444
+ if (key.ctrl && key.input === "o" && isStreaming && !pendingShell && !pendingPlan && !pendingReviseEditor && !pendingSessionsPicker && !pendingMcpHub && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
28445
+ setLiveExpand((v) => !v);
28446
+ return;
28447
+ }
28357
28448
  if (busy) return;
28358
28449
  if (pendingShell) return;
28359
28450
  if (atMatches && atMatches.length > 0) {
@@ -29124,7 +29215,12 @@ function AppInner({
29124
29215
  const m = loadSessionMeta(session);
29125
29216
  const cost2 = (m.totalCostUsd ?? 0) + (ev.stats?.cost ?? 0);
29126
29217
  const turn = (m.turnCount ?? 0) + 1;
29127
- patchSessionMeta(session, { totalCostUsd: cost2, turnCount: turn });
29218
+ const currency = walletCurrencyRef.current;
29219
+ patchSessionMeta(session, {
29220
+ totalCostUsd: cost2,
29221
+ turnCount: turn,
29222
+ ...currency ? { balanceCurrency: currency } : {}
29223
+ });
29128
29224
  }
29129
29225
  } else if (ev.role === "tool_start") {
29130
29226
  handleToolStart(ev, {
@@ -29632,7 +29728,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
29632
29728
  // changes (incoming stream, key press, modal popup).
29633
29729
  !busy && !isStreaming
29634
29730
  },
29635
- /* @__PURE__ */ React66.createElement(ViewportBudgetProvider, null, /* @__PURE__ */ React66.createElement(Box49, { flexDirection: "row" }, /* @__PURE__ */ React66.createElement(Box49, { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React66.createElement(Box49, { flexDirection: "column" }, /* @__PURE__ */ React66.createElement(CardStream, { suppressLive: modalOpen }), !hasConversation && !busy && !isStreaming ? /* @__PURE__ */ React66.createElement(
29731
+ /* @__PURE__ */ React66.createElement(ViewportBudgetProvider, null, /* @__PURE__ */ React66.createElement(Box49, { flexDirection: "row" }, /* @__PURE__ */ React66.createElement(Box49, { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React66.createElement(Box49, { flexDirection: "column" }, /* @__PURE__ */ React66.createElement(LiveExpandContext.Provider, { value: liveExpand }, /* @__PURE__ */ React66.createElement(CardStream, { suppressLive: modalOpen })), !hasConversation && !busy && !isStreaming ? /* @__PURE__ */ React66.createElement(
29636
29732
  WelcomeBanner,
29637
29733
  {
29638
29734
  inCodeMode: !!codeMode,
@@ -29695,6 +29791,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
29695
29791
  {
29696
29792
  sessions: sessionsPickerList,
29697
29793
  workspace: currentRootDir,
29794
+ walletCurrency: walletCurrencyRef.current,
29698
29795
  onChoose: (outcome) => {
29699
29796
  if (outcome.kind === "open") {
29700
29797
  setPendingSessionsPicker(false);