spacecode 2.0.2 → 2.1.1

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/repl.mjs CHANGED
@@ -1396,7 +1396,7 @@ var require_react_development = __commonJS({
1396
1396
  }
1397
1397
  return dispatcher.useContext(Context);
1398
1398
  }
1399
- function useState7(initialState) {
1399
+ function useState10(initialState) {
1400
1400
  var dispatcher = resolveDispatcher();
1401
1401
  return dispatcher.useState(initialState);
1402
1402
  }
@@ -1404,11 +1404,11 @@ var require_react_development = __commonJS({
1404
1404
  var dispatcher = resolveDispatcher();
1405
1405
  return dispatcher.useReducer(reducer, initialArg, init);
1406
1406
  }
1407
- function useRef3(initialValue) {
1407
+ function useRef4(initialValue) {
1408
1408
  var dispatcher = resolveDispatcher();
1409
1409
  return dispatcher.useRef(initialValue);
1410
1410
  }
1411
- function useEffect7(create2, deps) {
1411
+ function useEffect9(create2, deps) {
1412
1412
  var dispatcher = resolveDispatcher();
1413
1413
  return dispatcher.useEffect(create2, deps);
1414
1414
  }
@@ -1420,11 +1420,11 @@ var require_react_development = __commonJS({
1420
1420
  var dispatcher = resolveDispatcher();
1421
1421
  return dispatcher.useLayoutEffect(create2, deps);
1422
1422
  }
1423
- function useCallback3(callback, deps) {
1423
+ function useCallback4(callback, deps) {
1424
1424
  var dispatcher = resolveDispatcher();
1425
1425
  return dispatcher.useCallback(callback, deps);
1426
1426
  }
1427
- function useMemo7(create2, deps) {
1427
+ function useMemo8(create2, deps) {
1428
1428
  var dispatcher = resolveDispatcher();
1429
1429
  return dispatcher.useMemo(create2, deps);
1430
1430
  }
@@ -2187,19 +2187,19 @@ var require_react_development = __commonJS({
2187
2187
  exports.memo = memo;
2188
2188
  exports.startTransition = startTransition;
2189
2189
  exports.unstable_act = act;
2190
- exports.useCallback = useCallback3;
2190
+ exports.useCallback = useCallback4;
2191
2191
  exports.useContext = useContext7;
2192
2192
  exports.useDebugValue = useDebugValue;
2193
2193
  exports.useDeferredValue = useDeferredValue;
2194
- exports.useEffect = useEffect7;
2194
+ exports.useEffect = useEffect9;
2195
2195
  exports.useId = useId;
2196
2196
  exports.useImperativeHandle = useImperativeHandle;
2197
2197
  exports.useInsertionEffect = useInsertionEffect;
2198
2198
  exports.useLayoutEffect = useLayoutEffect2;
2199
- exports.useMemo = useMemo7;
2199
+ exports.useMemo = useMemo8;
2200
2200
  exports.useReducer = useReducer;
2201
- exports.useRef = useRef3;
2202
- exports.useState = useState7;
2201
+ exports.useRef = useRef4;
2202
+ exports.useState = useState10;
2203
2203
  exports.useSyncExternalStore = useSyncExternalStore;
2204
2204
  exports.useTransition = useTransition;
2205
2205
  exports.version = ReactVersion;
@@ -2255,7 +2255,7 @@ var require_react_jsx_runtime_development = __commonJS({
2255
2255
  if (process.env.NODE_ENV !== "production") {
2256
2256
  (function() {
2257
2257
  "use strict";
2258
- var React15 = require_react();
2258
+ var React19 = require_react();
2259
2259
  var REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("react.element");
2260
2260
  var REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for("react.portal");
2261
2261
  var REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment");
@@ -2281,7 +2281,7 @@ var require_react_jsx_runtime_development = __commonJS({
2281
2281
  }
2282
2282
  return null;
2283
2283
  }
2284
- var ReactSharedInternals = React15.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
2284
+ var ReactSharedInternals = React19.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
2285
2285
  function error(format) {
2286
2286
  {
2287
2287
  {
@@ -3131,11 +3131,11 @@ var require_react_jsx_runtime_development = __commonJS({
3131
3131
  return jsxWithValidation(type, props, key, false);
3132
3132
  }
3133
3133
  }
3134
- var jsx9 = jsxWithValidationDynamic;
3135
- var jsxs8 = jsxWithValidationStatic;
3134
+ var jsx12 = jsxWithValidationDynamic;
3135
+ var jsxs12 = jsxWithValidationStatic;
3136
3136
  exports.Fragment = REACT_FRAGMENT_TYPE;
3137
- exports.jsx = jsx9;
3138
- exports.jsxs = jsxs8;
3137
+ exports.jsx = jsx12;
3138
+ exports.jsxs = jsxs12;
3139
3139
  })();
3140
3140
  }
3141
3141
  }
@@ -10823,9 +10823,9 @@ var require_react_reconciler_development = __commonJS({
10823
10823
  module.exports = function $$$reconciler($$$hostConfig) {
10824
10824
  var exports2 = {};
10825
10825
  "use strict";
10826
- var React15 = require_react();
10826
+ var React19 = require_react();
10827
10827
  var Scheduler = require_scheduler();
10828
- var ReactSharedInternals = React15.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
10828
+ var ReactSharedInternals = React19.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
10829
10829
  var suppressWarning = false;
10830
10830
  function setSuppressWarning(newSuppressWarning) {
10831
10831
  {
@@ -25702,10 +25702,10 @@ var require_react_reconciler_development = __commonJS({
25702
25702
  var setErrorHandler = null;
25703
25703
  var setSuspenseHandler = null;
25704
25704
  {
25705
- var copyWithDeleteImpl = function(obj, path10, index2) {
25706
- var key = path10[index2];
25705
+ var copyWithDeleteImpl = function(obj, path11, index2) {
25706
+ var key = path11[index2];
25707
25707
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
25708
- if (index2 + 1 === path10.length) {
25708
+ if (index2 + 1 === path11.length) {
25709
25709
  if (isArray(updated)) {
25710
25710
  updated.splice(key, 1);
25711
25711
  } else {
@@ -25713,11 +25713,11 @@ var require_react_reconciler_development = __commonJS({
25713
25713
  }
25714
25714
  return updated;
25715
25715
  }
25716
- updated[key] = copyWithDeleteImpl(obj[key], path10, index2 + 1);
25716
+ updated[key] = copyWithDeleteImpl(obj[key], path11, index2 + 1);
25717
25717
  return updated;
25718
25718
  };
25719
- var copyWithDelete = function(obj, path10) {
25720
- return copyWithDeleteImpl(obj, path10, 0);
25719
+ var copyWithDelete = function(obj, path11) {
25720
+ return copyWithDeleteImpl(obj, path11, 0);
25721
25721
  };
25722
25722
  var copyWithRenameImpl = function(obj, oldPath, newPath, index2) {
25723
25723
  var oldKey = oldPath[index2];
@@ -25755,17 +25755,17 @@ var require_react_reconciler_development = __commonJS({
25755
25755
  }
25756
25756
  return copyWithRenameImpl(obj, oldPath, newPath, 0);
25757
25757
  };
25758
- var copyWithSetImpl = function(obj, path10, index2, value) {
25759
- if (index2 >= path10.length) {
25758
+ var copyWithSetImpl = function(obj, path11, index2, value) {
25759
+ if (index2 >= path11.length) {
25760
25760
  return value;
25761
25761
  }
25762
- var key = path10[index2];
25762
+ var key = path11[index2];
25763
25763
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
25764
- updated[key] = copyWithSetImpl(obj[key], path10, index2 + 1, value);
25764
+ updated[key] = copyWithSetImpl(obj[key], path11, index2 + 1, value);
25765
25765
  return updated;
25766
25766
  };
25767
- var copyWithSet = function(obj, path10, value) {
25768
- return copyWithSetImpl(obj, path10, 0, value);
25767
+ var copyWithSet = function(obj, path11, value) {
25768
+ return copyWithSetImpl(obj, path11, 0, value);
25769
25769
  };
25770
25770
  var findHook = function(fiber, id) {
25771
25771
  var currentHook2 = fiber.memoizedState;
@@ -25775,10 +25775,10 @@ var require_react_reconciler_development = __commonJS({
25775
25775
  }
25776
25776
  return currentHook2;
25777
25777
  };
25778
- overrideHookState = function(fiber, id, path10, value) {
25778
+ overrideHookState = function(fiber, id, path11, value) {
25779
25779
  var hook = findHook(fiber, id);
25780
25780
  if (hook !== null) {
25781
- var newState = copyWithSet(hook.memoizedState, path10, value);
25781
+ var newState = copyWithSet(hook.memoizedState, path11, value);
25782
25782
  hook.memoizedState = newState;
25783
25783
  hook.baseState = newState;
25784
25784
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -25788,10 +25788,10 @@ var require_react_reconciler_development = __commonJS({
25788
25788
  }
25789
25789
  }
25790
25790
  };
25791
- overrideHookStateDeletePath = function(fiber, id, path10) {
25791
+ overrideHookStateDeletePath = function(fiber, id, path11) {
25792
25792
  var hook = findHook(fiber, id);
25793
25793
  if (hook !== null) {
25794
- var newState = copyWithDelete(hook.memoizedState, path10);
25794
+ var newState = copyWithDelete(hook.memoizedState, path11);
25795
25795
  hook.memoizedState = newState;
25796
25796
  hook.baseState = newState;
25797
25797
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -25814,8 +25814,8 @@ var require_react_reconciler_development = __commonJS({
25814
25814
  }
25815
25815
  }
25816
25816
  };
25817
- overrideProps = function(fiber, path10, value) {
25818
- fiber.pendingProps = copyWithSet(fiber.memoizedProps, path10, value);
25817
+ overrideProps = function(fiber, path11, value) {
25818
+ fiber.pendingProps = copyWithSet(fiber.memoizedProps, path11, value);
25819
25819
  if (fiber.alternate) {
25820
25820
  fiber.alternate.pendingProps = fiber.pendingProps;
25821
25821
  }
@@ -25824,8 +25824,8 @@ var require_react_reconciler_development = __commonJS({
25824
25824
  scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
25825
25825
  }
25826
25826
  };
25827
- overridePropsDeletePath = function(fiber, path10) {
25828
- fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path10);
25827
+ overridePropsDeletePath = function(fiber, path11) {
25828
+ fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path11);
25829
25829
  if (fiber.alternate) {
25830
25830
  fiber.alternate.pendingProps = fiber.pendingProps;
25831
25831
  }
@@ -28903,24 +28903,24 @@ var require_sender = __commonJS({
28903
28903
  * @public
28904
28904
  */
28905
28905
  ping(data, mask, cb) {
28906
- let byteLength;
28906
+ let byteLength2;
28907
28907
  let readOnly;
28908
28908
  if (typeof data === "string") {
28909
- byteLength = Buffer.byteLength(data);
28909
+ byteLength2 = Buffer.byteLength(data);
28910
28910
  readOnly = false;
28911
28911
  } else if (isBlob(data)) {
28912
- byteLength = data.size;
28912
+ byteLength2 = data.size;
28913
28913
  readOnly = false;
28914
28914
  } else {
28915
28915
  data = toBuffer(data);
28916
- byteLength = data.length;
28916
+ byteLength2 = data.length;
28917
28917
  readOnly = toBuffer.readOnly;
28918
28918
  }
28919
- if (byteLength > 125) {
28919
+ if (byteLength2 > 125) {
28920
28920
  throw new RangeError("The data size must not be greater than 125 bytes");
28921
28921
  }
28922
28922
  const options = {
28923
- [kByteLength]: byteLength,
28923
+ [kByteLength]: byteLength2,
28924
28924
  fin: true,
28925
28925
  generateMask: this._generateMask,
28926
28926
  mask,
@@ -28950,24 +28950,24 @@ var require_sender = __commonJS({
28950
28950
  * @public
28951
28951
  */
28952
28952
  pong(data, mask, cb) {
28953
- let byteLength;
28953
+ let byteLength2;
28954
28954
  let readOnly;
28955
28955
  if (typeof data === "string") {
28956
- byteLength = Buffer.byteLength(data);
28956
+ byteLength2 = Buffer.byteLength(data);
28957
28957
  readOnly = false;
28958
28958
  } else if (isBlob(data)) {
28959
- byteLength = data.size;
28959
+ byteLength2 = data.size;
28960
28960
  readOnly = false;
28961
28961
  } else {
28962
28962
  data = toBuffer(data);
28963
- byteLength = data.length;
28963
+ byteLength2 = data.length;
28964
28964
  readOnly = toBuffer.readOnly;
28965
28965
  }
28966
- if (byteLength > 125) {
28966
+ if (byteLength2 > 125) {
28967
28967
  throw new RangeError("The data size must not be greater than 125 bytes");
28968
28968
  }
28969
28969
  const options = {
28970
- [kByteLength]: byteLength,
28970
+ [kByteLength]: byteLength2,
28971
28971
  fin: true,
28972
28972
  generateMask: this._generateMask,
28973
28973
  mask,
@@ -29008,23 +29008,23 @@ var require_sender = __commonJS({
29008
29008
  const perMessageDeflate = this._extensions[PerMessageDeflate2.extensionName];
29009
29009
  let opcode = options.binary ? 2 : 1;
29010
29010
  let rsv1 = options.compress;
29011
- let byteLength;
29011
+ let byteLength2;
29012
29012
  let readOnly;
29013
29013
  if (typeof data === "string") {
29014
- byteLength = Buffer.byteLength(data);
29014
+ byteLength2 = Buffer.byteLength(data);
29015
29015
  readOnly = false;
29016
29016
  } else if (isBlob(data)) {
29017
- byteLength = data.size;
29017
+ byteLength2 = data.size;
29018
29018
  readOnly = false;
29019
29019
  } else {
29020
29020
  data = toBuffer(data);
29021
- byteLength = data.length;
29021
+ byteLength2 = data.length;
29022
29022
  readOnly = toBuffer.readOnly;
29023
29023
  }
29024
29024
  if (this._firstFragment) {
29025
29025
  this._firstFragment = false;
29026
29026
  if (rsv1 && perMessageDeflate && perMessageDeflate.params[perMessageDeflate._isServer ? "server_no_context_takeover" : "client_no_context_takeover"]) {
29027
- rsv1 = byteLength >= perMessageDeflate._threshold;
29027
+ rsv1 = byteLength2 >= perMessageDeflate._threshold;
29028
29028
  }
29029
29029
  this._compress = rsv1;
29030
29030
  } else {
@@ -29033,7 +29033,7 @@ var require_sender = __commonJS({
29033
29033
  }
29034
29034
  if (options.fin) this._firstFragment = true;
29035
29035
  const opts = {
29036
- [kByteLength]: byteLength,
29036
+ [kByteLength]: byteLength2,
29037
29037
  fin: options.fin,
29038
29038
  generateMask: this._generateMask,
29039
29039
  mask: options.mask,
@@ -30543,7 +30543,7 @@ var require_stream = __commonJS({
30543
30543
  };
30544
30544
  duplex._final = function(callback) {
30545
30545
  if (ws.readyState === ws.CONNECTING) {
30546
- ws.once("open", function open2() {
30546
+ ws.once("open", function open3() {
30547
30547
  duplex._final(callback);
30548
30548
  });
30549
30549
  return;
@@ -30564,7 +30564,7 @@ var require_stream = __commonJS({
30564
30564
  };
30565
30565
  duplex._write = function(chunk, encoding, callback) {
30566
30566
  if (ws.readyState === ws.CONNECTING) {
30567
- ws.once("open", function open2() {
30567
+ ws.once("open", function open3() {
30568
30568
  duplex._write(chunk, encoding, callback);
30569
30569
  });
30570
30570
  return;
@@ -31950,18 +31950,18 @@ var init_source = __esm({
31950
31950
  }
31951
31951
  }
31952
31952
  });
31953
- createStyler = (open2, close, parent) => {
31953
+ createStyler = (open3, close, parent) => {
31954
31954
  let openAll;
31955
31955
  let closeAll;
31956
31956
  if (parent === void 0) {
31957
- openAll = open2;
31957
+ openAll = open3;
31958
31958
  closeAll = close;
31959
31959
  } else {
31960
- openAll = parent.openAll + open2;
31960
+ openAll = parent.openAll + open3;
31961
31961
  closeAll = close + parent.closeAll;
31962
31962
  }
31963
31963
  return {
31964
- open: open2,
31964
+ open: open3,
31965
31965
  close,
31966
31966
  openAll,
31967
31967
  closeAll,
@@ -33530,8 +33530,8 @@ var init_ErrorOverview = __esm({
33530
33530
  init_dist3();
33531
33531
  init_Box();
33532
33532
  init_Text();
33533
- cleanupPath = (path10) => {
33534
- return path10?.replace(`file://${cwd()}/`, "");
33533
+ cleanupPath = (path11) => {
33534
+ return path11?.replace(`file://${cwd()}/`, "");
33535
33535
  };
33536
33536
  stackUtils = new import_stack_utils.default({
33537
33537
  cwd: cwd(),
@@ -34606,6 +34606,47 @@ var init_build2 = __esm({
34606
34606
  }
34607
34607
  });
34608
34608
 
34609
+ // src/render/theme.ts
34610
+ function roleProps(role, theme) {
34611
+ switch (theme) {
34612
+ case "plain":
34613
+ return {};
34614
+ case "high-contrast":
34615
+ return role === "secondary" ? {} : { color: ROLE_COLORS[role], bold: true };
34616
+ case "auto":
34617
+ return { color: ROLE_COLORS[role] };
34618
+ }
34619
+ }
34620
+ function resolveThemeName(env3, warn = (m2) => console.warn(m2)) {
34621
+ const raw = env3.SPACECODE_THEME;
34622
+ if (raw === void 0 || raw === "") return "auto";
34623
+ if (THEME_NAMES.includes(raw)) return raw;
34624
+ warn(`[theme] unknown SPACECODE_THEME "${raw}"; using "auto". Accepted values: ${THEME_NAMES.join(", ")}.`);
34625
+ return "auto";
34626
+ }
34627
+ function activeTheme() {
34628
+ active ??= resolveThemeName(process.env);
34629
+ return active;
34630
+ }
34631
+ function paint(role) {
34632
+ return roleProps(role, activeTheme());
34633
+ }
34634
+ var THEME_NAMES, ROLE_COLORS, active;
34635
+ var init_theme = __esm({
34636
+ "src/render/theme.ts"() {
34637
+ "use strict";
34638
+ THEME_NAMES = ["auto", "plain", "high-contrast"];
34639
+ ROLE_COLORS = {
34640
+ agent: "magenta",
34641
+ add: "green",
34642
+ delete: "red",
34643
+ needsYou: "yellow",
34644
+ action: "blue",
34645
+ secondary: "gray"
34646
+ };
34647
+ }
34648
+ });
34649
+
34609
34650
  // src/components/WelcomeBanner.tsx
34610
34651
  function getSubtitle(mode, launchCount2) {
34611
34652
  switch (mode) {
@@ -34622,26 +34663,238 @@ function getSubtitle(mode, launchCount2) {
34622
34663
  return "Ready to code. What's next?";
34623
34664
  }
34624
34665
  }
34625
- function WelcomeBanner({ repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2 }) {
34626
- const subtitle = getSubtitle(onboardingMode2, launchCount2);
34627
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", marginBottom: 1, children: [
34628
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", children: BORDER }),
34629
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", bold: true, children: "SpaceCode Agent" }),
34630
- repoName2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
34631
- "Project: ",
34632
- repoName2
34633
- ] }),
34634
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "gray", children: subtitle }),
34635
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "cyan", children: BORDER })
34666
+ function WelcomeBanner({ onboardingMode: onboardingMode2, launchCount: launchCount2 }) {
34667
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { ...paint("secondary"), children: [
34668
+ getSubtitle(onboardingMode2, launchCount2),
34669
+ " /help for commands, @ to attach a file."
34636
34670
  ] });
34637
34671
  }
34638
- var import_jsx_runtime, BORDER;
34672
+ var import_jsx_runtime;
34639
34673
  var init_WelcomeBanner = __esm({
34640
34674
  async "src/components/WelcomeBanner.tsx"() {
34641
34675
  "use strict";
34642
34676
  await init_build2();
34677
+ init_theme();
34643
34678
  import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
34644
- BORDER = "\u2550".repeat(50);
34679
+ }
34680
+ });
34681
+
34682
+ // src/render/hints.ts
34683
+ function formatHints({ running, asking, columns }, sep2 = "\xB7") {
34684
+ columns = Math.min(columns, MAX_STATIC_LINE);
34685
+ const parts = asking ? ["1 2 3 answer", "esc refuse"] : running ? ["esc interrupt", "tab step", "shift+tab mode", "ctrl+o turns"] : ["enter send", "shift+tab mode", "/ commands", "@ file", "! shell", "ctrl+o turns"];
34686
+ const kept = [...parts];
34687
+ while (kept.length > 1 && kept.join(` ${sep2} `).length > columns) kept.pop();
34688
+ return kept.join(` ${sep2} `);
34689
+ }
34690
+ var MAX_STATIC_LINE;
34691
+ var init_hints = __esm({
34692
+ "src/render/hints.ts"() {
34693
+ "use strict";
34694
+ MAX_STATIC_LINE = 58;
34695
+ }
34696
+ });
34697
+
34698
+ // src/render/header.ts
34699
+ function formatDuration(ms) {
34700
+ const min = Math.floor(ms / 6e4);
34701
+ return min < 1 ? "<1 min" : min < 60 ? `${min} min` : `${Math.floor(min / 60)}h ${min % 60}m`;
34702
+ }
34703
+ function formatHeader({ project, git, otherSessions, turns, elapsedMs, columns: terminalColumns }, sep2 = "\xB7") {
34704
+ const columns = Math.min(terminalColumns, MAX_STATIC_LINE);
34705
+ const optional = [];
34706
+ const branch = [];
34707
+ if (git?.kind === "ok") {
34708
+ branch.push({ text: git.branch });
34709
+ if (git.dirty > 0) branch.push({ text: `${sep2 === "\xB7" ? "\xB1" : "+-"}${git.dirty}`, role: "needsYou" });
34710
+ } else if (git?.kind === "unknown") {
34711
+ branch.push({ text: "git ?", role: "needsYou" });
34712
+ }
34713
+ if (otherSessions !== void 0 && otherSessions > 0) optional.push({ text: `+${otherSessions} other session${otherSessions === 1 ? "" : "s"}`, role: "secondary" });
34714
+ optional.push({ text: `${turns} turn${turns === 1 ? "" : "s"}`, role: "secondary" });
34715
+ optional.push({ text: formatDuration(elapsedMs), role: "secondary" });
34716
+ const base = [{ text: "spacecode", bold: true }, { text: project }];
34717
+ const all = [...base, ...branch, ...optional];
34718
+ const width = (segs) => segs.reduce((n, s, i) => n + s.text.length + (i > 0 ? sep2.length + 2 : 0), 0);
34719
+ const kept = [...all];
34720
+ while (kept.length > base.length && width(kept) > columns) kept.pop();
34721
+ return kept;
34722
+ }
34723
+ var init_header = __esm({
34724
+ "src/render/header.ts"() {
34725
+ "use strict";
34726
+ init_hints();
34727
+ }
34728
+ });
34729
+
34730
+ // src/render/rail.ts
34731
+ function unicodeAvailable(env3, stdoutIsTTY) {
34732
+ if (!stdoutIsTTY) return false;
34733
+ const locale = env3.LC_ALL || env3.LC_CTYPE || env3.LANG || "";
34734
+ return /utf-?8/i.test(locale);
34735
+ }
34736
+ function railGlyphs(unicode) {
34737
+ return unicode ? UNICODE : ASCII;
34738
+ }
34739
+ function truncatePath(target, max, ellipsis = "\u2026") {
34740
+ if (max <= 0) return "";
34741
+ const e = ellipsis.length;
34742
+ if (target.length <= max) return target;
34743
+ const parts = target.split("/");
34744
+ const file = parts[parts.length - 1];
34745
+ if (parts.length > 1 && file.length + e + 1 <= max) {
34746
+ let tail = file;
34747
+ for (let i = parts.length - 2; i >= 0; i--) {
34748
+ const next = `${parts[i]}/${tail}`;
34749
+ if (next.length + e + 1 > max) break;
34750
+ tail = next;
34751
+ }
34752
+ return `${ellipsis}/${tail}`;
34753
+ }
34754
+ return max <= e ? ellipsis.slice(0, max) : `${target.slice(0, max - e)}${ellipsis}`;
34755
+ }
34756
+ function fitStepLine({ verb, target, meta, columns, indent = 4, ellipsis = "\u2026", sep: sep2 = "\xB7" }) {
34757
+ const room = Math.max(10, columns - indent);
34758
+ const metaText = meta ? ` ${sep2} ${meta}` : "";
34759
+ const fixed = verb.length + 1;
34760
+ if (fixed + target.length + metaText.length <= room) return { verb, target, meta };
34761
+ const targetRoom = room - fixed - metaText.length;
34762
+ const fileName = target.split("/").pop() ?? target;
34763
+ if (meta && targetRoom >= Math.min(fileName.length, 12) + 2) {
34764
+ return { verb, target: truncatePath(target, targetRoom, ellipsis), meta };
34765
+ }
34766
+ return { verb, target: truncatePath(target, Math.max(1, room - fixed), ellipsis) };
34767
+ }
34768
+ function plainOutput() {
34769
+ return unicodeOverride === void 0 && !process.stdout.isTTY;
34770
+ }
34771
+ function activeGlyphs() {
34772
+ return railGlyphs(unicodeOverride ?? unicodeAvailable(process.env, Boolean(process.stdout.isTTY)));
34773
+ }
34774
+ var UNICODE, ASCII, unicodeOverride;
34775
+ var init_rail = __esm({
34776
+ "src/render/rail.ts"() {
34777
+ "use strict";
34778
+ UNICODE = { rail: "\u2502", result: "\u23BF", ellipsis: "\u2026", sep: "\xB7", marker: { running: "\u25D0", waiting: "\u25C7", ok: "\u25CF", failed: "\u2717" } };
34779
+ ASCII = { rail: "|", result: ">", ellipsis: "...", sep: "-", marker: { running: "o", waiting: "?", ok: "*", failed: "x" } };
34780
+ }
34781
+ });
34782
+
34783
+ // src/components/Header.tsx
34784
+ function Header(props) {
34785
+ const sep2 = activeGlyphs().sep;
34786
+ const segments2 = formatHeader(props, sep2);
34787
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { children: segments2.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react22.default.Fragment, { children: [
34788
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { ...paint("secondary"), children: ` ${sep2} ` }),
34789
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { bold: seg.bold, ...seg.role ? paint(seg.role) : {}, children: seg.text })
34790
+ ] }, i)) });
34791
+ }
34792
+ var import_react22, import_jsx_runtime2;
34793
+ var init_Header = __esm({
34794
+ async "src/components/Header.tsx"() {
34795
+ "use strict";
34796
+ import_react22 = __toESM(require_react(), 1);
34797
+ await init_build2();
34798
+ init_header();
34799
+ init_rail();
34800
+ init_theme();
34801
+ import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
34802
+ }
34803
+ });
34804
+
34805
+ // src/components/ErrorBlock.tsx
34806
+ function ErrorBlock({ view }) {
34807
+ const glyphs = activeGlyphs();
34808
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
34809
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { ...paint("delete"), bold: true, children: `${glyphs.marker.failed} ${view.title}` }),
34810
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: ` ${view.cause}` }),
34811
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { ...paint("secondary"), children: ` ${view.action}` })
34812
+ ] });
34813
+ }
34814
+ var import_jsx_runtime3;
34815
+ var init_ErrorBlock = __esm({
34816
+ async "src/components/ErrorBlock.tsx"() {
34817
+ "use strict";
34818
+ await init_build2();
34819
+ init_rail();
34820
+ init_theme();
34821
+ import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
34822
+ }
34823
+ });
34824
+
34825
+ // src/render/git-status.ts
34826
+ function parseGitStatus(output) {
34827
+ const lines2 = output.split("\n").filter((l) => l !== "");
34828
+ const head = lines2.find((l) => l.startsWith("## "));
34829
+ const dirty = lines2.filter((l) => !l.startsWith("## ")).length;
34830
+ if (!head) throw new Error("git status output has no branch line");
34831
+ const rest = head.slice(3);
34832
+ let branch;
34833
+ if (rest.startsWith("No commits yet on ")) branch = rest.slice("No commits yet on ".length);
34834
+ else if (rest.startsWith("HEAD (no branch)")) branch = "detached HEAD";
34835
+ else if (rest.startsWith("Initial commit on ")) branch = rest.slice("Initial commit on ".length);
34836
+ else branch = rest.split("...")[0].split(" ")[0];
34837
+ return { branch, dirty };
34838
+ }
34839
+ async function readGitState(run) {
34840
+ try {
34841
+ const { branch, dirty } = parseGitStatus(await run(["status", "--porcelain=v1", "-b"]));
34842
+ return { kind: "ok", branch, dirty };
34843
+ } catch (err) {
34844
+ const text = `${err instanceof Error ? err.message : String(err)} ${err?.stderr ?? ""}`;
34845
+ return /not a git repository/i.test(text) ? { kind: "none" } : { kind: "unknown" };
34846
+ }
34847
+ }
34848
+ var init_git_status = __esm({
34849
+ "src/render/git-status.ts"() {
34850
+ "use strict";
34851
+ }
34852
+ });
34853
+
34854
+ // src/render/node-git.ts
34855
+ import { execFile } from "node:child_process";
34856
+ function createNodeGitRunner(cwd2, timeoutMs = 1e3) {
34857
+ return (args) => new Promise((resolve2, reject) => {
34858
+ execFile("git", args, { cwd: cwd2, timeout: timeoutMs, maxBuffer: 1024 * 1024, env: { ...process.env, LC_ALL: "C", GIT_OPTIONAL_LOCKS: "0" } }, (err, stdout, stderr) => {
34859
+ if (err) reject(Object.assign(err, { stderr }));
34860
+ else resolve2(stdout);
34861
+ });
34862
+ });
34863
+ }
34864
+ var init_node_git = __esm({
34865
+ "src/render/node-git.ts"() {
34866
+ "use strict";
34867
+ }
34868
+ });
34869
+
34870
+ // src/render/mode-line.ts
34871
+ function modeGlyph(mode, unicode = true) {
34872
+ return unicode ? GLYPHS[mode].unicode : GLYPHS[mode].ascii;
34873
+ }
34874
+ function promptGlyph(mode, unicode = true) {
34875
+ return mode === "plan" || mode === "yolo" ? modeGlyph(mode, unicode) : ">";
34876
+ }
34877
+ function modeLine(mode, unicode = true) {
34878
+ const label = unicode ? LABELS[mode] : LABELS[mode].replace(" \xB7 ", " - ");
34879
+ const role = mode === "accept-edits" ? "add" : mode === "plan" ? "action" : mode === "yolo" ? "delete" : void 0;
34880
+ return { text: `${modeGlyph(mode, unicode)} ${label}`, role };
34881
+ }
34882
+ var GLYPHS, LABELS;
34883
+ var init_mode_line = __esm({
34884
+ "src/render/mode-line.ts"() {
34885
+ "use strict";
34886
+ GLYPHS = {
34887
+ default: { unicode: "\u25C7", ascii: "-" },
34888
+ "accept-edits": { unicode: "\u25C8", ascii: "+" },
34889
+ plan: { unicode: "\u25A2", ascii: "#" },
34890
+ yolo: { unicode: "!", ascii: "!" }
34891
+ };
34892
+ LABELS = {
34893
+ default: "default",
34894
+ "accept-edits": "accept edits",
34895
+ plan: "plan \xB7 read-only",
34896
+ yolo: "yolo"
34897
+ };
34645
34898
  }
34646
34899
  });
34647
34900
 
@@ -34764,13 +35017,13 @@ function processEvents(events) {
34764
35017
  return { renderOrder, statusMessage };
34765
35018
  }
34766
35019
  function useEventAccumulator(events) {
34767
- return (0, import_react22.useMemo)(() => processEvents(events), [events]);
35020
+ return (0, import_react23.useMemo)(() => processEvents(events), [events]);
34768
35021
  }
34769
- var import_react22, MAX_OUTPUT_LINES, MAX_RESULT_OUTPUT;
35022
+ var import_react23, MAX_OUTPUT_LINES, MAX_RESULT_OUTPUT;
34770
35023
  var init_useEventAccumulator = __esm({
34771
35024
  "src/components/useEventAccumulator.ts"() {
34772
35025
  "use strict";
34773
- import_react22 = __toESM(require_react(), 1);
35026
+ import_react23 = __toESM(require_react(), 1);
34774
35027
  MAX_OUTPUT_LINES = 3;
34775
35028
  MAX_RESULT_OUTPUT = 500;
34776
35029
  }
@@ -38638,7 +38891,7 @@ var require_core = __commonJS({
38638
38891
  }
38639
38892
  return highlighted[0].event === "start" ? original : highlighted;
38640
38893
  }
38641
- function open2(node) {
38894
+ function open3(node) {
38642
38895
  function attributeString(attr) {
38643
38896
  return " " + attr.nodeName + '="' + escapeHTML(attr.value) + '"';
38644
38897
  }
@@ -38648,7 +38901,7 @@ var require_core = __commonJS({
38648
38901
  result += "</" + tag(node) + ">";
38649
38902
  }
38650
38903
  function render3(event) {
38651
- (event.event === "start" ? open2 : close)(event.node);
38904
+ (event.event === "start" ? open3 : close)(event.node);
38652
38905
  }
38653
38906
  while (original.length || highlighted.length) {
38654
38907
  let stream = selectStream();
@@ -38660,7 +38913,7 @@ var require_core = __commonJS({
38660
38913
  render3(stream.splice(0, 1)[0]);
38661
38914
  stream = selectStream();
38662
38915
  } while (stream === original && stream.length && stream[0].offset === processed);
38663
- nodeStack.reverse().forEach(open2);
38916
+ nodeStack.reverse().forEach(open3);
38664
38917
  } else {
38665
38918
  if (stream[0].event === "start") {
38666
38919
  nodeStack.push(stream[0].node);
@@ -61906,11 +62159,11 @@ var require_perl = __commonJS({
61906
62159
  /#/
61907
62160
  // valid but infrequent and weird
61908
62161
  ];
61909
- const PAIRED_DOUBLE_RE = (prefix, open2, close = "\\1") => {
61910
- const middle = close === "\\1" ? close : concat(close, open2);
62162
+ const PAIRED_DOUBLE_RE = (prefix, open3, close = "\\1") => {
62163
+ const middle = close === "\\1" ? close : concat(close, open3);
61911
62164
  return concat(
61912
62165
  concat("(?:", prefix, ")"),
61913
- open2,
62166
+ open3,
61914
62167
  /(?:\\.|[^\\\/])*?/,
61915
62168
  middle,
61916
62169
  /(?:\\.|[^\\\/])*?/,
@@ -61918,10 +62171,10 @@ var require_perl = __commonJS({
61918
62171
  REGEX_MODIFIERS
61919
62172
  );
61920
62173
  };
61921
- const PAIRED_RE = (prefix, open2, close) => {
62174
+ const PAIRED_RE = (prefix, open3, close) => {
61922
62175
  return concat(
61923
62176
  concat("(?:", prefix, ")"),
61924
- open2,
62177
+ open3,
61925
62178
  /(?:\\.|[^\\\/])*?/,
61926
62179
  close,
61927
62180
  REGEX_MODIFIERS
@@ -80629,15 +80882,15 @@ var require_route = __commonJS({
80629
80882
  };
80630
80883
  }
80631
80884
  function wrapConversion(toModel, graph) {
80632
- const path10 = [graph[toModel].parent, toModel];
80885
+ const path11 = [graph[toModel].parent, toModel];
80633
80886
  let fn = conversions[graph[toModel].parent][toModel];
80634
80887
  let cur = graph[toModel].parent;
80635
80888
  while (graph[cur].parent) {
80636
- path10.unshift(graph[cur].parent);
80889
+ path11.unshift(graph[cur].parent);
80637
80890
  fn = link2(conversions[graph[cur].parent][cur], fn);
80638
80891
  cur = graph[cur].parent;
80639
80892
  }
80640
- fn.conversion = path10;
80893
+ fn.conversion = path11;
80641
80894
  return fn;
80642
80895
  }
80643
80896
  module.exports = function(fromModel) {
@@ -81226,18 +81479,18 @@ var require_source = __commonJS({
81226
81479
  }
81227
81480
  }
81228
81481
  });
81229
- var createStyler2 = (open2, close, parent) => {
81482
+ var createStyler2 = (open3, close, parent) => {
81230
81483
  let openAll;
81231
81484
  let closeAll;
81232
81485
  if (parent === void 0) {
81233
- openAll = open2;
81486
+ openAll = open3;
81234
81487
  closeAll = close;
81235
81488
  } else {
81236
- openAll = parent.openAll + open2;
81489
+ openAll = parent.openAll + open3;
81237
81490
  closeAll = close + parent.closeAll;
81238
81491
  }
81239
81492
  return {
81240
- open: open2,
81493
+ open: open3,
81241
81494
  close,
81242
81495
  openAll,
81243
81496
  closeAll,
@@ -94055,8 +94308,8 @@ var init_markdown = __esm({
94055
94308
  // src/useTerminalSize.ts
94056
94309
  function useTerminalSize(fallback = { columns: 80, rows: 24 }) {
94057
94310
  const { stdout } = use_stdout_default();
94058
- const [size, setSize] = (0, import_react23.useState)({ columns: stdout?.columns || fallback.columns, rows: stdout?.rows || fallback.rows });
94059
- (0, import_react23.useEffect)(() => {
94311
+ const [size, setSize] = (0, import_react24.useState)({ columns: stdout?.columns || fallback.columns, rows: stdout?.rows || fallback.rows });
94312
+ (0, import_react24.useEffect)(() => {
94060
94313
  if (!stdout) return;
94061
94314
  const update = () => setSize({ columns: stdout.columns || fallback.columns, rows: stdout.rows || fallback.rows });
94062
94315
  update();
@@ -94067,11 +94320,11 @@ function useTerminalSize(fallback = { columns: 80, rows: 24 }) {
94067
94320
  }, [stdout, fallback.columns, fallback.rows]);
94068
94321
  return size;
94069
94322
  }
94070
- var import_react23;
94323
+ var import_react24;
94071
94324
  var init_useTerminalSize = __esm({
94072
94325
  async "src/useTerminalSize.ts"() {
94073
94326
  "use strict";
94074
- import_react23 = __toESM(require_react(), 1);
94327
+ import_react24 = __toESM(require_react(), 1);
94075
94328
  await init_build2();
94076
94329
  }
94077
94330
  });
@@ -94085,22 +94338,22 @@ function tailLines(text, maxLines) {
94085
94338
  }
94086
94339
  function MarkdownBlock({ content, maxLines }) {
94087
94340
  const { columns } = useTerminalSize();
94088
- const rendered = (0, import_react24.useMemo)(() => {
94341
+ const rendered = (0, import_react25.useMemo)(() => {
94089
94342
  const out = renderMarkdown(content, { columns });
94090
94343
  return (maxLines && maxLines >= 2 ? tailLines(out, maxLines) : out) || null;
94091
94344
  }, [content, columns, maxLines]);
94092
94345
  if (rendered === null) return null;
94093
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: rendered });
94346
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { children: rendered });
94094
94347
  }
94095
- var import_react24, import_jsx_runtime2;
94348
+ var import_react25, import_jsx_runtime4;
94096
94349
  var init_MarkdownBlock = __esm({
94097
94350
  async "src/components/MarkdownBlock.tsx"() {
94098
94351
  "use strict";
94099
- import_react24 = __toESM(require_react(), 1);
94352
+ import_react25 = __toESM(require_react(), 1);
94100
94353
  await init_build2();
94101
94354
  init_markdown();
94102
94355
  await init_useTerminalSize();
94103
- import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
94356
+ import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
94104
94357
  }
94105
94358
  });
94106
94359
 
@@ -95751,9 +96004,9 @@ var require_cli_spinners = __commonJS({
95751
96004
 
95752
96005
  // ../../node_modules/ink-spinner/build/index.js
95753
96006
  function Spinner({ type = "dots" }) {
95754
- const [frame, setFrame] = (0, import_react25.useState)(0);
96007
+ const [frame, setFrame] = (0, import_react26.useState)(0);
95755
96008
  const spinner = import_cli_spinners.default[type];
95756
- (0, import_react25.useEffect)(() => {
96009
+ (0, import_react26.useEffect)(() => {
95757
96010
  const timer = setInterval(() => {
95758
96011
  setFrame((previousFrame) => {
95759
96012
  const isLastFrame = previousFrame === spinner.frames.length - 1;
@@ -95764,12 +96017,12 @@ function Spinner({ type = "dots" }) {
95764
96017
  clearInterval(timer);
95765
96018
  };
95766
96019
  }, [spinner]);
95767
- return import_react25.default.createElement(Text, null, spinner.frames[frame]);
96020
+ return import_react26.default.createElement(Text, null, spinner.frames[frame]);
95768
96021
  }
95769
- var import_react25, import_cli_spinners, build_default;
96022
+ var import_react26, import_cli_spinners, build_default;
95770
96023
  var init_build3 = __esm({
95771
96024
  async "../../node_modules/ink-spinner/build/index.js"() {
95772
- import_react25 = __toESM(require_react(), 1);
96025
+ import_react26 = __toESM(require_react(), 1);
95773
96026
  await init_build2();
95774
96027
  import_cli_spinners = __toESM(require_cli_spinners(), 1);
95775
96028
  build_default = Spinner;
@@ -95777,48 +96030,238 @@ var init_build3 = __esm({
95777
96030
  });
95778
96031
 
95779
96032
  // src/render/tool-header.ts
95780
- function toolHeader(tool, input) {
96033
+ function stepParts(tool, input) {
95781
96034
  if (tool === "todo_write") {
95782
96035
  const n = Array.isArray(input.todos) ? input.todos.length : 0;
95783
- return `Todos(${n} item${n === 1 ? "" : "s"})`;
95784
- }
95785
- const LABELS = {
95786
- read_file: { label: "Read", field: "path" },
95787
- write_file: { label: "Write", field: "path" },
95788
- edit_file: { label: "Update", field: "path" },
95789
- list_directory: { label: "List", field: "path" },
95790
- search: { label: "Search", field: "query" },
95791
- bash: { label: "Bash", field: "command" }
95792
- };
95793
- const mapping = LABELS[tool];
95794
- if (mapping) {
95795
- const rawValue = input[mapping.field];
95796
- let value2 = "";
95797
- if (typeof rawValue === "string") {
95798
- if (tool === "bash") {
95799
- const firstLine = rawValue.split("\n")[0];
95800
- value2 = firstLine.length > 60 ? firstLine.slice(0, 60) + "\u2026" : firstLine;
95801
- } else {
95802
- value2 = rawValue;
95803
- }
95804
- }
95805
- return `${mapping.label}(${value2})`;
96036
+ return { verb: "Todos", target: `${n} item${n === 1 ? "" : "s"}` };
95806
96037
  }
95807
- let value = "";
95808
- for (const [, v2] of Object.entries(input)) {
95809
- if (typeof v2 === "string") {
95810
- value = v2;
95811
- break;
95812
- }
95813
- }
95814
- if (value.length > 40) {
95815
- value = value.slice(0, 40);
95816
- }
95817
- return `${tool}(${value})`;
96038
+ const field = { read_file: "path", write_file: "path", edit_file: "path", list_directory: "path", search: "query", bash: "command" }[tool];
96039
+ const raw = field ? input[field] : Object.values(input).find((v2) => typeof v2 === "string");
96040
+ let target = typeof raw === "string" ? raw : "";
96041
+ if (tool === "bash") target = target.split("\n")[0];
96042
+ return { verb: STEP_VERBS[tool] ?? tool, target };
95818
96043
  }
96044
+ var STEP_VERBS;
95819
96045
  var init_tool_header = __esm({
95820
96046
  "src/render/tool-header.ts"() {
95821
96047
  "use strict";
96048
+ STEP_VERBS = {
96049
+ read_file: "Read",
96050
+ write_file: "Write",
96051
+ edit_file: "Edit",
96052
+ list_directory: "List",
96053
+ search: "Search",
96054
+ bash: "Run",
96055
+ todo_write: "Todos"
96056
+ };
96057
+ }
96058
+ });
96059
+
96060
+ // src/editor/credential-filter.ts
96061
+ function containsCredential(entry) {
96062
+ return PATTERNS.some((re3) => re3.test(entry));
96063
+ }
96064
+ var PATTERNS;
96065
+ var init_credential_filter = __esm({
96066
+ "src/editor/credential-filter.ts"() {
96067
+ "use strict";
96068
+ PATTERNS = [
96069
+ /\bsk[_-][A-Za-z0-9_-]{16,}/,
96070
+ /\bgh[pos]_[A-Za-z0-9]{20,}/,
96071
+ /\bgithub_pat_[A-Za-z0-9_]{20,}/,
96072
+ /\bAKIA[0-9A-Z]{16}\b/,
96073
+ /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}/,
96074
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/,
96075
+ /\bxox[abp]-[A-Za-z0-9-]{10,}/,
96076
+ /\bnpm_[A-Za-z0-9]{30,}/,
96077
+ /\b[A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*=\S{8,}/i
96078
+ ];
96079
+ }
96080
+ });
96081
+
96082
+ // src/session/session-store.ts
96083
+ import { chmod, mkdir, open, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
96084
+ import { randomBytes } from "node:crypto";
96085
+ import path from "node:path";
96086
+ function redactCredentials(value) {
96087
+ if (typeof value === "string") return containsCredential(value) ? REDACTED : value;
96088
+ if (Array.isArray(value)) return value.map(redactCredentials);
96089
+ if (value && typeof value === "object") {
96090
+ return Object.fromEntries(Object.entries(value).map(([k2, v2]) => [k2, redactCredentials(v2)]));
96091
+ }
96092
+ return value;
96093
+ }
96094
+ function parseSession(file, text) {
96095
+ const lines2 = text.split("\n").filter((l) => l.length > 0);
96096
+ if (lines2.length === 0) throw new SessionError(file, "Session file is empty");
96097
+ let parsed;
96098
+ try {
96099
+ parsed = lines2.map((l) => JSON.parse(l));
96100
+ } catch (err) {
96101
+ throw new SessionError(file, `Corrupt session file: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
96102
+ }
96103
+ const meta = parsed[0];
96104
+ if (meta.v !== 1 || typeof meta.id !== "string" || typeof meta.projectRoot !== "string" || typeof meta.updatedAt !== "string") {
96105
+ throw new SessionError(file, "Unrecognized session header (expected v1 with id, projectRoot, updatedAt)");
96106
+ }
96107
+ const warnings = [];
96108
+ if (meta.todos !== void 0 && !(Array.isArray(meta.todos) && meta.todos.every((t) => t && typeof t.content === "string" && ["pending", "in_progress", "completed"].includes(t.status)))) {
96109
+ delete meta.todos;
96110
+ warnings.push(`The saved checklist in ${file} was not understood (saved by a different version?) and was left out.`);
96111
+ }
96112
+ const messages = parsed.slice(1);
96113
+ if (!messages.every((m2) => m2 && (m2.role === "user" || m2.role === "assistant"))) {
96114
+ throw new SessionError(file, "Corrupt session file: a message has no valid role");
96115
+ }
96116
+ return warnings.length > 0 ? { meta, messages, warnings } : { meta, messages };
96117
+ }
96118
+ function formatAge(fromIso, now) {
96119
+ const secs = Math.max(0, Math.floor((now.getTime() - new Date(fromIso).getTime()) / 1e3));
96120
+ if (secs < 60) return `${secs}s ago`;
96121
+ if (secs < 3600) return `${Math.floor(secs / 60)}m ago`;
96122
+ if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`;
96123
+ return `${Math.floor(secs / 86400)}d ago`;
96124
+ }
96125
+ function titleFromMessages(messages) {
96126
+ const first = messages.find((m2) => m2.role === "user");
96127
+ const text = !first ? "" : typeof first.content === "string" ? first.content : first.content.map((b2) => b2.text ?? "").join(" ");
96128
+ const oneLine = text.replace(/\s+/g, " ").trim();
96129
+ return oneLine.length > 60 ? `${oneLine.slice(0, 59)}\u2026` : oneLine || "(untitled)";
96130
+ }
96131
+ function countTurns(messages) {
96132
+ return messages.filter((m2) => m2.role === "user" && (typeof m2.content === "string" || m2.content.some((b2) => b2.type === "text"))).length;
96133
+ }
96134
+ var MAX_SESSION_BYTES, PREVIEW_TAIL_BYTES, REDACTED, SessionError, ID, SessionStore;
96135
+ var init_session_store = __esm({
96136
+ "src/session/session-store.ts"() {
96137
+ "use strict";
96138
+ init_credential_filter();
96139
+ MAX_SESSION_BYTES = 5 * 1024 * 1024;
96140
+ PREVIEW_TAIL_BYTES = 256 * 1024;
96141
+ REDACTED = "[redacted: credential-shaped content]";
96142
+ SessionError = class extends Error {
96143
+ constructor(file, message, options) {
96144
+ super(`${message} (${file})`, options);
96145
+ this.file = file;
96146
+ this.name = "SessionError";
96147
+ }
96148
+ };
96149
+ ID = /^[A-Za-z0-9_-]{8,64}$/;
96150
+ SessionStore = class {
96151
+ constructor(dir, now = () => /* @__PURE__ */ new Date()) {
96152
+ this.dir = dir;
96153
+ this.now = now;
96154
+ }
96155
+ newId() {
96156
+ return `${this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomBytes(4).toString("hex")}`;
96157
+ }
96158
+ fileFor(id) {
96159
+ if (!ID.test(id)) throw new SessionError(this.dir, `Invalid session id ${JSON.stringify(id)}`);
96160
+ return path.join(this.dir, `${id}.jsonl`);
96161
+ }
96162
+ async save(session) {
96163
+ const file = this.fileFor(session.meta.id);
96164
+ const meta = { ...session.meta, updatedAt: this.now().toISOString() };
96165
+ const body = [meta, ...session.messages].map((entry) => JSON.stringify(redactCredentials(entry))).join("\n") + "\n";
96166
+ const bytes = Buffer.byteLength(body);
96167
+ if (bytes > MAX_SESSION_BYTES) {
96168
+ throw new SessionError(file, `Session is ${bytes} bytes, over the ${MAX_SESSION_BYTES}-byte cap; use /compact or /clear`);
96169
+ }
96170
+ const tmp = `${file}.${process.pid}.tmp`;
96171
+ try {
96172
+ await mkdir(this.dir, { recursive: true, mode: 448 });
96173
+ await chmod(this.dir, 448);
96174
+ await writeFile(tmp, body, { mode: 384 });
96175
+ await chmod(tmp, 384);
96176
+ await rename(tmp, file);
96177
+ } catch (err) {
96178
+ throw new SessionError(file, `Could not save session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
96179
+ }
96180
+ }
96181
+ async load(id) {
96182
+ const file = this.fileFor(id);
96183
+ let text;
96184
+ try {
96185
+ const { size } = await stat(file);
96186
+ if (size > MAX_SESSION_BYTES) {
96187
+ throw new SessionError(file, `Session is ${size} bytes, over the ${MAX_SESSION_BYTES}-byte cap; not loaded`);
96188
+ }
96189
+ text = await readFile(file, "utf8");
96190
+ } catch (err) {
96191
+ if (err instanceof SessionError) throw err;
96192
+ throw new SessionError(file, `Could not read session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
96193
+ }
96194
+ const session = parseSession(file, text);
96195
+ if (session.meta.id !== id) throw new SessionError(file, `Session header id "${session.meta.id}" does not match the file name`);
96196
+ return session;
96197
+ }
96198
+ /** Newest first. Unreadable files are reported, not skipped silently. */
96199
+ async list() {
96200
+ let names;
96201
+ try {
96202
+ names = await readdir(this.dir);
96203
+ } catch (err) {
96204
+ if (err.code === "ENOENT") return { sessions: [], errors: [] };
96205
+ throw new SessionError(this.dir, `Could not list sessions: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
96206
+ }
96207
+ const sessions = [];
96208
+ const errors = [];
96209
+ for (const name of names.filter((n) => n.endsWith(".jsonl"))) {
96210
+ try {
96211
+ sessions.push((await this.load(name.slice(0, -".jsonl".length))).meta);
96212
+ } catch (err) {
96213
+ errors.push(err instanceof SessionError ? err : new SessionError(path.join(this.dir, name), String(err), { cause: err }));
96214
+ }
96215
+ }
96216
+ sessions.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
96217
+ return { sessions, errors };
96218
+ }
96219
+ /**
96220
+ * The last assistant text of a session (first 120 chars, credential-redacted), read from the end of the file so a
96221
+ * large session is not loaded whole. null when none is found in the tail.
96222
+ */
96223
+ async preview(id) {
96224
+ const file = this.fileFor(id);
96225
+ try {
96226
+ const { size } = await stat(file);
96227
+ const length = Math.min(size, PREVIEW_TAIL_BYTES);
96228
+ const handle = await open(file, "r");
96229
+ let tail;
96230
+ try {
96231
+ const buf = Buffer.alloc(length);
96232
+ await handle.read(buf, 0, length, size - length);
96233
+ tail = buf.toString("utf8");
96234
+ } finally {
96235
+ await handle.close();
96236
+ }
96237
+ const lines2 = tail.split("\n").filter((l) => l.length > 0);
96238
+ if (length < size) lines2.shift();
96239
+ for (let i = lines2.length - 1; i >= 0; i--) {
96240
+ let msg;
96241
+ try {
96242
+ msg = JSON.parse(lines2[i]);
96243
+ } catch {
96244
+ continue;
96245
+ }
96246
+ if (msg.role !== "assistant") continue;
96247
+ const text = typeof msg.content === "string" ? msg.content : msg.content.map((b2) => b2.text ?? "").join(" ");
96248
+ const oneLine = redactCredentials(text.replace(/\s+/g, " ").trim());
96249
+ if (oneLine) return oneLine.length > 120 ? `${oneLine.slice(0, 119)}\u2026` : oneLine;
96250
+ }
96251
+ return null;
96252
+ } catch (err) {
96253
+ throw new SessionError(file, `Could not read session preview: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
96254
+ }
96255
+ }
96256
+ async delete(id) {
96257
+ const file = this.fileFor(id);
96258
+ try {
96259
+ await rm(file);
96260
+ } catch (err) {
96261
+ throw new SessionError(file, `Could not delete session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
96262
+ }
96263
+ }
96264
+ };
95822
96265
  }
95823
96266
  });
95824
96267
 
@@ -95865,6 +96308,287 @@ var init_diff_rows = __esm({
95865
96308
  }
95866
96309
  });
95867
96310
 
96311
+ // src/render/step-record.ts
96312
+ function cutToBytes(text, max) {
96313
+ let out = text;
96314
+ while (byteLength(out) > max) out = out.slice(0, Math.max(0, Math.floor(out.length * (max / byteLength(out)) * 0.98) - 1));
96315
+ const last = out.charCodeAt(out.length - 1);
96316
+ if (last >= 55296 && last <= 56319) out = out.slice(0, -1);
96317
+ return out;
96318
+ }
96319
+ function redactLines(raw) {
96320
+ let inKey = false;
96321
+ return raw.split("\n").map((line) => {
96322
+ if (inKey) {
96323
+ if (PEM_END.test(line)) inKey = false;
96324
+ return REDACTED;
96325
+ }
96326
+ if (PEM_BEGIN.test(line)) {
96327
+ inKey = !PEM_END.test(line);
96328
+ return REDACTED;
96329
+ }
96330
+ return containsCredential(line) ? REDACTED : line;
96331
+ });
96332
+ }
96333
+ function capDetail(raw) {
96334
+ const lines2 = redactLines(raw);
96335
+ const kept = lines2.slice(0, MAX_DETAIL_LINES);
96336
+ let text = kept.join("\n");
96337
+ const notes = [];
96338
+ if (lines2.length > kept.length) notes.push(`${lines2.length - kept.length} more line${lines2.length - kept.length === 1 ? "" : "s"} not kept`);
96339
+ if (byteLength(text) > MAX_DETAIL_BYTES) {
96340
+ text = cutToBytes(text, MAX_DETAIL_BYTES);
96341
+ notes.push(`truncated to ${MAX_DETAIL_BYTES / 1024} KB`);
96342
+ }
96343
+ return notes.length > 0 ? `${text}
96344
+ \u2026 ${notes.join("; ")}` : text;
96345
+ }
96346
+ function detailOf(state) {
96347
+ const { tool, input, result, outputLines } = state;
96348
+ if (tool === "edit_file" && typeof input.old_string === "string" && typeof input.new_string === "string") {
96349
+ const { rows, hidden } = diffRows(input.old_string, input.new_string, MAX_DETAIL_LINES);
96350
+ const body = rows.map((r) => `${String(r.n).padStart(4)} ${r.kind === "add" ? "+" : "-"} ${r.text}`).join("\n");
96351
+ return hidden > 0 ? `${body}
96352
+ \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} not kept` : body;
96353
+ }
96354
+ if (tool === "write_file" && typeof input.content === "string") return input.content;
96355
+ if (result) return result.output;
96356
+ return outputLines.length > 0 ? outputLines.join("\n") : null;
96357
+ }
96358
+ function metaOf(state) {
96359
+ const { result } = state;
96360
+ const parts = [];
96361
+ if (state.status === "error") parts.push("failed");
96362
+ if (result?.duration_ms != null) parts.push(`${(result.duration_ms / 1e3).toFixed(1)}s`);
96363
+ return parts.join(" \xB7 ");
96364
+ }
96365
+ function toStepRecord(state) {
96366
+ const { verb, target } = stepParts(state.tool, state.input);
96367
+ const raw = detailOf(state);
96368
+ return {
96369
+ id: state.id,
96370
+ tool: state.tool,
96371
+ verb,
96372
+ target,
96373
+ state: state.status === "running" ? "running" : state.status === "error" ? "failed" : "ok",
96374
+ meta: metaOf(state),
96375
+ durationMs: state.result?.duration_ms,
96376
+ detail: raw === null ? null : capDetail(raw)
96377
+ };
96378
+ }
96379
+ var MAX_DETAIL_LINES, MAX_DETAIL_BYTES, MAX_SESSION_BYTES2, NOT_KEPT, encoder, byteLength, PEM_BEGIN, PEM_END, StepStore;
96380
+ var init_step_record = __esm({
96381
+ "src/render/step-record.ts"() {
96382
+ "use strict";
96383
+ init_session_store();
96384
+ init_credential_filter();
96385
+ init_diff_rows();
96386
+ init_tool_header();
96387
+ MAX_DETAIL_LINES = 200;
96388
+ MAX_DETAIL_BYTES = 20 * 1024;
96389
+ MAX_SESSION_BYTES2 = 5 * 1024 * 1024;
96390
+ NOT_KEPT = "output not kept";
96391
+ encoder = new TextEncoder();
96392
+ byteLength = (s) => encoder.encode(s).length;
96393
+ PEM_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/;
96394
+ PEM_END = /-----END [A-Z ]*PRIVATE KEY-----/;
96395
+ StepStore = class {
96396
+ constructor(maxBytes = MAX_SESSION_BYTES2) {
96397
+ this.maxBytes = maxBytes;
96398
+ }
96399
+ records = [];
96400
+ bytes = 0;
96401
+ /** Insert or replace by id (a running step is updated as it finishes). */
96402
+ upsert(record) {
96403
+ const at = this.records.findIndex((r) => r.id === record.id);
96404
+ if (at >= 0) {
96405
+ this.bytes -= byteLength(this.records[at].detail ?? "");
96406
+ this.records[at] = record;
96407
+ } else {
96408
+ this.records.push(record);
96409
+ }
96410
+ this.bytes += byteLength(record.detail ?? "");
96411
+ for (let i = 0; i < this.records.length && this.bytes > this.maxBytes; i++) {
96412
+ const old = this.records[i];
96413
+ if (old.id === record.id || old.detail === null) continue;
96414
+ this.bytes -= byteLength(old.detail);
96415
+ this.records[i] = { ...old, detail: null };
96416
+ }
96417
+ }
96418
+ get(id) {
96419
+ return this.records.find((r) => r.id === id);
96420
+ }
96421
+ all() {
96422
+ return this.records;
96423
+ }
96424
+ get detailBytes() {
96425
+ return this.bytes;
96426
+ }
96427
+ };
96428
+ }
96429
+ });
96430
+
96431
+ // src/render/describe-error.ts
96432
+ function sanitizeCause(raw) {
96433
+ const lines2 = raw.split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => !/^\s*at\s+\S+\s+\(.*:\d+:\d+\)\s*$|^\s*at\s+\S+:\d+:\d+\s*$/.test(l)).slice(0, MAX_CAUSE_LINES);
96434
+ const safe = lines2.map((l) => containsCredential(l) ? "[redacted: credential-shaped content]" : l).join(" ");
96435
+ return safe.length > MAX_CAUSE_CHARS ? `${safe.slice(0, MAX_CAUSE_CHARS - 1)}\u2026` : safe || "No details were reported.";
96436
+ }
96437
+ function classifyError(message) {
96438
+ const api = /^API error (\d{3})\b/.exec(message);
96439
+ if (api) {
96440
+ const status = Number(api[1]);
96441
+ if (status === 401 || status === 403) return { kind: "auth", status };
96442
+ if (status === 402) return { kind: "payment", status };
96443
+ if (status === 429) return { kind: "rate-limit", status };
96444
+ if (status >= 500) return { kind: "gateway", status };
96445
+ return { kind: "unknown", status };
96446
+ }
96447
+ if (/^No API key found/.test(message)) return { kind: "missing-key" };
96448
+ if (/^Not approved\b/.test(message)) return { kind: "policy" };
96449
+ if (NETWORK.test(message)) return { kind: "network" };
96450
+ return { kind: "unknown" };
96451
+ }
96452
+ function bodyMessage(cause) {
96453
+ const json = cause.slice(cause.indexOf(":") + 1).trim();
96454
+ try {
96455
+ const parsed = JSON.parse(json);
96456
+ const m2 = parsed.error?.message ?? parsed.message;
96457
+ return typeof m2 === "string" && m2.trim() ? m2.trim() : null;
96458
+ } catch {
96459
+ return null;
96460
+ }
96461
+ }
96462
+ function defaultHost() {
96463
+ try {
96464
+ return new URL(process.env.API_PROXY_URL || "https://api.spacecode.app").host;
96465
+ } catch {
96466
+ return "the gateway";
96467
+ }
96468
+ }
96469
+ function describeError(kind, cause, ctx = {}) {
96470
+ const retained = ctx.retained === true;
96471
+ const shown = sanitizeCause(cause);
96472
+ const status = ctx.status;
96473
+ switch (kind) {
96474
+ case "gateway":
96475
+ return {
96476
+ title: `Gateway didn't answer${status ? ` (${status})` : ""}`,
96477
+ cause: `${ctx.host ?? defaultHost()} is unavailable.${retained ? " Your prompt is saved." : ""}`,
96478
+ action: retained ? "Press enter to retry, or run /status to check the gateway." : "Run /status to check the gateway, then send your prompt again."
96479
+ };
96480
+ case "rate-limit":
96481
+ return {
96482
+ title: `Rate limited${status ? ` (${status})` : ""}`,
96483
+ cause: `The gateway is getting too many requests; it already retried a few times.${retained ? " Your prompt is saved." : ""}`,
96484
+ action: retained ? "Wait a moment, then press enter to retry." : "Wait a moment, then send your prompt again."
96485
+ };
96486
+ case "auth":
96487
+ return {
96488
+ title: `Sign-in rejected${status ? ` (${status})` : ""}`,
96489
+ cause: "The gateway did not accept your key.",
96490
+ action: retained ? "Run `spacecode login` in another terminal, then press enter to retry." : "Run `spacecode login`, then send your prompt again."
96491
+ };
96492
+ case "missing-key":
96493
+ return {
96494
+ title: "No API key",
96495
+ cause: "The REPL needs a key to call the gateway.",
96496
+ action: retained ? "Run `spacecode login` in another terminal or set SPACECODE_API_KEY, then press enter to retry." : "Run `spacecode login` or set SPACECODE_API_KEY, then send your prompt again."
96497
+ };
96498
+ case "payment":
96499
+ return {
96500
+ title: `Payment required${status ? ` (${status})` : ""}`,
96501
+ cause: sanitizeCause(bodyMessage(cause) ?? "Your plan does not include this."),
96502
+ action: `Open ${BILLING_URL} to choose a plan.`
96503
+ };
96504
+ case "network":
96505
+ return {
96506
+ title: "Can't reach the gateway",
96507
+ cause: `${shown}${retained ? " Your prompt is saved." : ""}`,
96508
+ action: retained ? "Check your connection, then press enter to retry." : "Check your connection, then send your prompt again."
96509
+ };
96510
+ case "session-save":
96511
+ return {
96512
+ title: "Session not saved",
96513
+ cause: shown,
96514
+ action: "Your conversation continues. Free some space or run /compact; the next turn tries to save again."
96515
+ };
96516
+ case "policy":
96517
+ return {
96518
+ title: "Not approved",
96519
+ cause: shown.replace(/^Not approved\s*\(([^)]*)\):\s*(.*)$/, "$2 ($1)").replace(/^Not approved\s*/, ""),
96520
+ action: "Press shift+tab to change the permission mode, or ask the agent to take another approach."
96521
+ };
96522
+ case "unknown":
96523
+ return { title: "Something went wrong", cause: shown, action: "Run /status for the session state, or send your prompt again." };
96524
+ }
96525
+ }
96526
+ function describeEventError(message, ctx = {}) {
96527
+ const { kind, status } = classifyError(message);
96528
+ return describeError(kind, message, { ...ctx, status: ctx.status ?? status });
96529
+ }
96530
+ function isRetryable(message) {
96531
+ const { kind } = classifyError(message);
96532
+ return kind === "gateway" || kind === "rate-limit" || kind === "network" || kind === "missing-key" || kind === "auth";
96533
+ }
96534
+ var BILLING_URL, MAX_CAUSE_LINES, MAX_CAUSE_CHARS, NETWORK;
96535
+ var init_describe_error = __esm({
96536
+ "src/render/describe-error.ts"() {
96537
+ "use strict";
96538
+ init_credential_filter();
96539
+ BILLING_URL = "https://spacecode.g-a-l-a-c-t-i-c.com";
96540
+ MAX_CAUSE_LINES = 3;
96541
+ MAX_CAUSE_CHARS = 300;
96542
+ NETWORK = /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|network (error|request failed|is unreachable)|socket hang up/i;
96543
+ }
96544
+ });
96545
+
96546
+ // src/render/step-view.ts
96547
+ function stepsOfEvents(events) {
96548
+ return processEvents(events).renderOrder.flatMap((item) => item.type === "tool" ? [toStepRecord(item.state)] : []);
96549
+ }
96550
+ function stepsOfTurns(turns, maxBytes) {
96551
+ const store = new StepStore(maxBytes);
96552
+ for (const turn of turns) for (const record of stepsOfEvents(turn.events)) store.upsert({ ...record, id: `${turn.id}:${record.id}` });
96553
+ const byTurn = /* @__PURE__ */ new Map();
96554
+ for (const record of store.all()) {
96555
+ const turnId = Number(record.id.slice(0, record.id.indexOf(":")));
96556
+ const list2 = byTurn.get(turnId) ?? [];
96557
+ list2.push({ ...record, id: record.id.slice(record.id.indexOf(":") + 1) });
96558
+ byTurn.set(turnId, list2);
96559
+ }
96560
+ return byTurn;
96561
+ }
96562
+ function detailWindow(detail, maxLines, width, offset = 0) {
96563
+ if (detail === null) return { lines: [NOT_KEPT], above: 0, below: 0 };
96564
+ const rows = [];
96565
+ for (const line of detail.split("\n")) {
96566
+ if (line.length <= width) rows.push(line);
96567
+ else for (let i = 0; i < line.length; i += width) rows.push(line.slice(i, i + width));
96568
+ }
96569
+ const room = Math.max(1, maxLines);
96570
+ const start = Math.min(Math.max(0, offset), Math.max(0, rows.length - room));
96571
+ return { lines: rows.slice(start, start + room), above: start, below: Math.max(0, rows.length - start - room) };
96572
+ }
96573
+ function findBlockedStep(steps, req) {
96574
+ const tools = TOOLS_FOR_KIND[req.kind];
96575
+ if (!tools) return void 0;
96576
+ const subject = req.kind === "exec" ? req.subject.split("\n")[0] : req.subject;
96577
+ return steps.find((r) => {
96578
+ if (r.state !== "running" || !tools.includes(r.tool)) return false;
96579
+ return r.target === subject;
96580
+ });
96581
+ }
96582
+ var TOOLS_FOR_KIND;
96583
+ var init_step_view = __esm({
96584
+ "src/render/step-view.ts"() {
96585
+ "use strict";
96586
+ init_useEventAccumulator();
96587
+ init_step_record();
96588
+ TOOLS_FOR_KIND = { exec: ["bash"], edit_file: ["edit_file"], write_file: ["write_file"] };
96589
+ }
96590
+ });
96591
+
95868
96592
  // src/render/highlight.ts
95869
96593
  function languageFor(filePath) {
95870
96594
  if (!filePath) return null;
@@ -95916,111 +96640,156 @@ var init_highlight = __esm({
95916
96640
  });
95917
96641
 
95918
96642
  // src/components/ToolCallBlock.tsx
95919
- function formatDuration(ms) {
96643
+ function formatDuration2(ms) {
95920
96644
  return (ms / 1e3).toFixed(1) + "s";
95921
96645
  }
95922
- function summarize(output) {
96646
+ function summarize(output, ellipsis = "\u2026") {
95923
96647
  const lines2 = output.split("\n").filter((l) => l.trim().length > 0);
95924
96648
  if (lines2.length === 0) return "(no output)";
95925
- const first = lines2[0].length > MAX_SUMMARY_CHARS ? lines2[0].slice(0, MAX_SUMMARY_CHARS) + "\u2026" : lines2[0];
96649
+ const first = lines2[0].length > MAX_SUMMARY_CHARS ? lines2[0].slice(0, MAX_SUMMARY_CHARS) + ellipsis : lines2[0];
95926
96650
  return lines2.length > 1 ? `${first} (+${lines2.length - 1} lines)` : first;
95927
96651
  }
95928
96652
  function DiffBlock({ oldStr, newStr, filePath }) {
95929
96653
  const { rows, hidden } = diffRows(oldStr, newStr);
95930
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
96654
+ const ellipsis = activeGlyphs().ellipsis;
96655
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
95931
96656
  rows.map((row, i) => {
95932
96657
  const isAdd = row.kind === "add";
95933
- const color = isAdd ? "green" : "red";
95934
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { children: [
95935
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color, children: [
96658
+ const role = isAdd ? "add" : "delete";
96659
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
96660
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint(role), children: [
95936
96661
  " ",
95937
96662
  String(row.n).padStart(4),
95938
96663
  " ",
95939
96664
  isAdd ? "+" : "-",
95940
96665
  " "
95941
96666
  ] }),
95942
- isAdd ? highlightLine(row.text, filePath) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color, children: row.text })
96667
+ isAdd ? highlightLine(row.text, filePath) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint(role), children: row.text })
95943
96668
  ] }, i);
95944
96669
  }),
95945
- hidden > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "gray", children: [
95946
- " \u2026 +",
96670
+ hidden > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint("secondary"), children: [
96671
+ ` ${ellipsis} +`,
95947
96672
  hidden,
95948
96673
  " lines"
95949
96674
  ] })
95950
96675
  ] });
95951
96676
  }
95952
- function ToolCallBlock({ state }) {
96677
+ function ToolCallBlock({ state, focused = false, open: open3 = false, detailRows = 12, waiting = false }) {
95953
96678
  const { tool, input, status, outputLines, result } = state;
95954
- const header = toolHeader(tool, input);
96679
+ const { columns } = useTerminalSize();
96680
+ const glyphs = activeGlyphs();
96681
+ const plain = plainOutput();
96682
+ const unicode = glyphs.rail === "\u2502";
96683
+ const ellipsis = glyphs.ellipsis;
96684
+ const parts = stepParts(tool, input);
96685
+ const twist = open3 ? unicode ? "\u25BE" : "v" : unicode ? "\u25B8" : ">";
96686
+ if (status === "running" && waiting) {
96687
+ const line2 = fitStepLine({ ...parts, meta: "waiting for you", columns, indent: plain ? 0 : 4, ellipsis, sep: glyphs.sep });
96688
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96689
+ !plain && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint("needsYou"), children: [
96690
+ glyphs.marker.waiting,
96691
+ " "
96692
+ ] }),
96693
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
96694
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("action"), children: line2.verb }),
96695
+ " ",
96696
+ line2.target
96697
+ ] }),
96698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("needsYou"), children: ` ${glyphs.sep} waiting for you` })
96699
+ ] });
96700
+ }
95955
96701
  if (status === "running") {
95956
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
95957
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
95958
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "cyan", children: [
95959
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(build_default, { type: "dots" }),
96702
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
96703
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96704
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint("action"), children: [
96705
+ plain ? null : unicode ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(build_default, { type: "dots" }) : glyphs.marker.running,
96706
+ focused ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, children: twist }) : plain ? "" : " ",
96707
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, inverse: focused, children: parts.verb }),
95960
96708
  " ",
95961
- header
96709
+ fitStepLine({ ...parts, meta: "running", columns, indent: plain ? 0 : 4, ellipsis, sep: glyphs.sep }).target
95962
96710
  ] }),
95963
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", children: " \u2014 running..." })
96711
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("secondary"), children: ` ${glyphs.sep} running` })
95964
96712
  ] }),
95965
- outputLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "gray", children: [
96713
+ open3 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StepDetail, { state, rows: detailRows, columns }),
96714
+ !open3 && outputLines.map((line2, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint("secondary"), children: [
95966
96715
  " ",
95967
- "\u2502",
96716
+ glyphs.rail,
95968
96717
  " ",
95969
- line.length > MAX_RUNNING_LINE_CHARS ? `${line.slice(0, MAX_RUNNING_LINE_CHARS)}\u2026 (+${line.length - MAX_RUNNING_LINE_CHARS} chars)` : line
96718
+ line2.length > MAX_RUNNING_LINE_CHARS ? `${line2.slice(0, MAX_RUNNING_LINE_CHARS)}${ellipsis} (+${line2.length - MAX_RUNNING_LINE_CHARS} chars)` : line2
95970
96719
  ] }, i))
95971
96720
  ] });
95972
96721
  }
95973
96722
  const isError = status === "error";
95974
- const durationStr = result?.duration_ms != null ? ` (${formatDuration(result.duration_ms)})` : "";
96723
+ const metaText = [isError ? "failed" : "", result?.duration_ms != null ? formatDuration2(result.duration_ms) : ""].filter(Boolean).join(` ${glyphs.sep} `);
96724
+ const line = fitStepLine({ ...parts, meta: metaText || void 0, columns, indent: plain ? 0 : 4, ellipsis, sep: glyphs.sep });
95975
96725
  const output = result?.output ?? "";
95976
96726
  let body = null;
95977
96727
  if (isError && output) {
95978
96728
  const lines2 = output.split("\n");
96729
+ const policyRefusal = /^Not approved\b/.test(output);
95979
96730
  const shown = lines2.slice(0, MAX_ERROR_LINES);
95980
96731
  const more = lines2.length - shown.length;
95981
- body = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
95982
- shown.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "red", children: [
95983
- " \u23BF ",
96732
+ body = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
96733
+ shown.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint("delete"), children: [
96734
+ ` ${glyphs.result} `,
95984
96735
  l
95985
96736
  ] }, i)),
95986
- more > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "gray", children: [
95987
- " \u2026 ",
96737
+ more > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint("secondary"), children: [
96738
+ ` ${ellipsis} `,
95988
96739
  more,
95989
96740
  " more"
95990
- ] })
96741
+ ] }),
96742
+ policyRefusal && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("secondary"), children: ` ${glyphs.result} ${describeError("policy", output).action}` })
95991
96743
  ] });
95992
96744
  } else if (tool === "edit_file" && typeof input.old_string === "string" && typeof input.new_string === "string") {
95993
- body = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DiffBlock, { oldStr: input.old_string, newStr: input.new_string, filePath: typeof input.path === "string" ? input.path : void 0 });
96745
+ body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DiffBlock, { oldStr: input.old_string, newStr: input.new_string, filePath: typeof input.path === "string" ? input.path : void 0 });
95994
96746
  } else if (tool === "write_file" && typeof input.content === "string") {
95995
- body = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DiffBlock, { oldStr: "", newStr: input.content, filePath: typeof input.path === "string" ? input.path : void 0 });
96747
+ body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DiffBlock, { oldStr: "", newStr: input.content, filePath: typeof input.path === "string" ? input.path : void 0 });
95996
96748
  } else if (output) {
95997
- body = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "gray", children: [
95998
- " \u23BF ",
95999
- summarize(output)
96749
+ body = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { ...paint("secondary"), children: [
96750
+ ` ${glyphs.result} `,
96751
+ summarize(output, ellipsis)
96000
96752
  ] });
96001
96753
  }
96002
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
96003
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
96004
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isError ? "red" : "green", children: "\u25CF" }),
96005
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { children: [
96754
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", children: [
96755
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96756
+ !plain && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint(isError ? "delete" : "add"), children: isError ? glyphs.marker.failed : glyphs.marker.ok }),
96757
+ !plain && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: focused, children: focused ? twist : " " }),
96758
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { inverse: focused, children: [
96759
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("action"), children: line.verb }),
96006
96760
  " ",
96007
- header
96761
+ line.target
96008
96762
  ] }),
96009
- durationStr && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", children: durationStr })
96763
+ line.meta && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("secondary"), children: ` ${glyphs.sep} ${line.meta}` })
96010
96764
  ] }),
96011
- body
96765
+ open3 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StepDetail, { state, rows: detailRows, columns }) : body
96766
+ ] });
96767
+ }
96768
+ function StepDetail({ state, rows, columns }) {
96769
+ const record = toStepRecord(state);
96770
+ const detail = record.detail ?? (state.status === "running" ? "no output yet" : null);
96771
+ const win = detailWindow(detail, rows, Math.max(20, columns - 8));
96772
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", marginLeft: 3, children: [
96773
+ win.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("secondary"), children: l }, i)),
96774
+ win.below > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { ...paint("secondary"), children: `${activeGlyphs().ellipsis} ${win.below} more line${win.below === 1 ? "" : "s"}` })
96012
96775
  ] });
96013
96776
  }
96014
- var import_jsx_runtime3, MAX_ERROR_LINES, MAX_RUNNING_LINE_CHARS, MAX_SUMMARY_CHARS;
96777
+ var import_jsx_runtime5, MAX_ERROR_LINES, MAX_RUNNING_LINE_CHARS, MAX_SUMMARY_CHARS;
96015
96778
  var init_ToolCallBlock = __esm({
96016
96779
  async "src/components/ToolCallBlock.tsx"() {
96017
96780
  "use strict";
96018
96781
  await init_build2();
96019
96782
  await init_build3();
96020
96783
  init_tool_header();
96784
+ init_rail();
96785
+ await init_useTerminalSize();
96786
+ init_step_record();
96787
+ init_describe_error();
96788
+ init_step_view();
96021
96789
  init_diff_rows();
96022
96790
  init_highlight();
96023
- import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
96791
+ init_theme();
96792
+ import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
96024
96793
  MAX_ERROR_LINES = 8;
96025
96794
  MAX_RUNNING_LINE_CHARS = 200;
96026
96795
  MAX_SUMMARY_CHARS = 100;
@@ -96028,54 +96797,114 @@ var init_ToolCallBlock = __esm({
96028
96797
  });
96029
96798
 
96030
96799
  // src/components/EventRenderer.tsx
96031
- function EventRenderer({ events, liveMaxLines }) {
96800
+ function EventRenderer({ events, liveMaxLines, focusId = null, openId = null, detailRows, waitingId = null, prompt, promptSaved = false, deferErrors = false }) {
96032
96801
  const { renderOrder, statusMessage } = useEventAccumulator(events);
96033
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", children: [
96034
- renderOrder.map((item) => {
96802
+ const glyphs = activeGlyphs();
96803
+ const railStyle = { topLeft: "", top: "", topRight: "", right: "", bottomRight: "", bottom: "", bottomLeft: "", left: glyphs.rail };
96804
+ const groups = [];
96805
+ for (const item of renderOrder) {
96806
+ const last = groups[groups.length - 1];
96807
+ if (item.type === "tool" && Array.isArray(last)) last.push(item);
96808
+ else groups.push(item.type === "tool" ? [item] : item);
96809
+ }
96810
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", children: [
96811
+ groups.map((group) => {
96812
+ if (Array.isArray(group) && plainOutput()) {
96813
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { flexDirection: "column", children: group.map((item2) => item2.type === "tool" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react27.default.Fragment, { children: [
96814
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToolCallBlock, { state: item2.state, focused: item2.id === focusId, open: item2.id === openId, detailRows, waiting: item2.id === waitingId }),
96815
+ item2.id === waitingId && prompt
96816
+ ] }, item2.id)) }, group[0].id);
96817
+ }
96818
+ if (Array.isArray(group)) {
96819
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
96820
+ Box_default,
96821
+ {
96822
+ flexDirection: "column",
96823
+ borderStyle: railStyle,
96824
+ borderTop: false,
96825
+ borderBottom: false,
96826
+ borderRight: false,
96827
+ borderColor: paint("secondary").color,
96828
+ paddingLeft: 1,
96829
+ children: group.map((item2) => item2.type === "tool" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react27.default.Fragment, { children: [
96830
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToolCallBlock, { state: item2.state, focused: item2.id === focusId, open: item2.id === openId, detailRows, waiting: item2.id === waitingId }),
96831
+ item2.id === waitingId && prompt
96832
+ ] }, item2.id))
96833
+ },
96834
+ group[0].id
96835
+ );
96836
+ }
96837
+ const item = group;
96035
96838
  switch (item.type) {
96036
96839
  case "text":
96037
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MarkdownBlock, { content: item.content, maxLines: liveMaxLines }) }, item.id);
96840
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MarkdownBlock, { content: item.content, maxLines: liveMaxLines }) }, item.id);
96038
96841
  case "tool":
96039
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolCallBlock, { state: item.state }) }, item.id);
96842
+ return null;
96843
+ // grouped onto the rail above
96040
96844
  case "error":
96041
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "red", children: [
96042
- "Error: ",
96043
- item.message
96044
- ] }) }, item.id);
96845
+ if (deferErrors) return null;
96846
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorBlock, { view: describeEventError(item.message, { retained: promptSaved }) }) }, item.id);
96045
96847
  default: {
96046
96848
  const _exhaustive = item;
96047
96849
  return _exhaustive;
96048
96850
  }
96049
96851
  }
96050
96852
  }),
96051
- statusMessage && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "yellow", children: [
96052
- "\u2022 ",
96853
+ statusMessage && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { ...paint("needsYou"), children: [
96854
+ `${glyphs.rail === "\u2502" ? "\u2022" : "*"} `,
96053
96855
  statusMessage
96054
96856
  ] })
96055
96857
  ] });
96056
96858
  }
96057
- var import_jsx_runtime4;
96859
+ var import_react27, import_jsx_runtime6;
96058
96860
  var init_EventRenderer = __esm({
96059
96861
  async "src/components/EventRenderer.tsx"() {
96060
96862
  "use strict";
96863
+ import_react27 = __toESM(require_react(), 1);
96061
96864
  await init_build2();
96062
96865
  init_useEventAccumulator();
96063
96866
  await init_MarkdownBlock();
96064
96867
  await init_ToolCallBlock();
96065
- import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
96868
+ init_theme();
96869
+ init_rail();
96870
+ await init_ErrorBlock();
96871
+ init_describe_error();
96872
+ import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
96873
+ }
96874
+ });
96875
+
96876
+ // src/render/describe-approval.ts
96877
+ function describeApproval(req) {
96878
+ const reason = req.reason.trim();
96879
+ const sentence = reason === "" ? "" : `${reason[0].toUpperCase()}${reason.slice(1)}${/[.!?]$/.test(reason) ? "" : "."}`;
96880
+ switch (req.kind) {
96881
+ case "exec":
96882
+ return sentence !== "" ? `Runs a shell command. ${sentence}` : "Runs a shell command.";
96883
+ case "edit_file":
96884
+ return sentence !== "" ? `Edits a file in your project. ${sentence}` : "Edits a file in your project.";
96885
+ case "write_file":
96886
+ return sentence !== "" ? `Writes a file in your project. ${sentence}` : "Writes a file in your project.";
96887
+ default:
96888
+ return sentence !== "" ? sentence : "Needs your approval.";
96889
+ }
96890
+ }
96891
+ var init_describe_approval = __esm({
96892
+ "src/render/describe-approval.ts"() {
96893
+ "use strict";
96066
96894
  }
96067
96895
  });
96068
96896
 
96069
96897
  // src/components/PermissionPrompt.tsx
96070
96898
  function permissionOptions(req) {
96071
- const second = req.kind === "exec" ? `Yes, and don't ask again for this exact command` : "Yes, and allow all edits this session";
96899
+ const second = req.kind === "exec" ? `Yes, and don't ask again for this exact command this session` : "Yes, and allow all edits this session";
96072
96900
  return ["Yes", second, "No, tell the agent what to do instead"];
96073
96901
  }
96074
96902
  function PermissionPrompt({ req, onDecide }) {
96075
- const [selected, setSelected] = (0, import_react26.useState)(0);
96076
- const [typing, setTyping] = (0, import_react26.useState)(false);
96077
- const [feedback, setFeedback] = (0, import_react26.useState)("");
96903
+ const [selected, setSelected] = (0, import_react28.useState)(0);
96904
+ const [typing, setTyping] = (0, import_react28.useState)(false);
96905
+ const [feedback, setFeedback] = (0, import_react28.useState)("");
96078
96906
  const options = permissionOptions(req);
96907
+ const unicode = activeGlyphs().rail === "\u2502";
96079
96908
  const { isRawModeSupported } = use_stdin_default();
96080
96909
  const choose = (index) => {
96081
96910
  if (index === 0) onDecide({ approved: true });
@@ -96107,39 +96936,42 @@ function PermissionPrompt({ req, onDecide }) {
96107
96936
  }
96108
96937
  }, { isActive: Boolean(isRawModeSupported) });
96109
96938
  const title = req.kind === "exec" ? "Run command?" : req.kind === "edit_file" ? "Edit file?" : "Write file?";
96110
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
96111
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "yellow", children: title }),
96112
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: req.subject }),
96113
- req.preview && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DiffBlock, { oldStr: req.preview.oldText, newStr: req.preview.newText, filePath: req.subject }),
96114
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { dimColor: true, children: [
96115
- req.reason,
96939
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", borderStyle: unicode ? "round" : "classic", borderColor: paint("needsYou").color, paddingX: 1, children: [
96940
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, ...paint("needsYou"), children: title }),
96941
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: req.subject }),
96942
+ req.preview && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DiffBlock, { oldStr: req.preview.oldText, newStr: req.preview.newText, filePath: req.subject }),
96943
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { ...paint("secondary"), children: [
96944
+ describeApproval(req),
96116
96945
  " (",
96117
96946
  req.rule,
96118
96947
  ")"
96119
96948
  ] }),
96120
- options.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: i === selected ? "cyan" : void 0, children: [
96121
- i === selected ? "\u276F " : " ",
96949
+ options.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { ...i === selected ? paint("action") : {}, children: [
96950
+ i === selected ? unicode ? "\u276F " : "> " : " ",
96122
96951
  i + 1,
96123
96952
  ". ",
96124
96953
  label
96125
96954
  ] }, label)),
96126
- typing ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
96955
+ typing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { children: [
96127
96956
  "Tell the agent what to do instead: ",
96128
96957
  feedback,
96129
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { inverse: true, children: " " }),
96958
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { inverse: true, children: " " }),
96130
96959
  " ",
96131
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "(Enter to send, Esc to go back)" })
96132
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "\u2191/\u2193 Enter or 1-3 to choose \xB7 Esc to refuse" })
96960
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { ...paint("secondary"), children: "(Enter to send, Esc to go back)" })
96961
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { ...paint("secondary"), children: unicode ? "\u2191/\u2193 Enter or 1-3 to choose \xB7 Esc to refuse" : "up/down Enter or 1-3 to choose - Esc to refuse" })
96133
96962
  ] });
96134
96963
  }
96135
- var import_react26, import_jsx_runtime5;
96964
+ var import_react28, import_jsx_runtime7;
96136
96965
  var init_PermissionPrompt = __esm({
96137
96966
  async "src/components/PermissionPrompt.tsx"() {
96138
96967
  "use strict";
96139
- import_react26 = __toESM(require_react(), 1);
96968
+ import_react28 = __toESM(require_react(), 1);
96140
96969
  await init_build2();
96141
96970
  await init_ToolCallBlock();
96142
- import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
96971
+ init_describe_approval();
96972
+ init_rail();
96973
+ init_theme();
96974
+ import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
96143
96975
  }
96144
96976
  });
96145
96977
 
@@ -96147,10 +96979,10 @@ var init_PermissionPrompt = __esm({
96147
96979
  function TodoChecklist({ todos }) {
96148
96980
  if (todos.length === 0) return null;
96149
96981
  const done = todos.filter((t) => t.status === "completed").length;
96150
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
96151
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { bold: true, children: [
96982
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
96983
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { bold: true, children: [
96152
96984
  "Tasks ",
96153
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { dimColor: true, children: [
96985
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { ...paint("secondary"), children: [
96154
96986
  "(",
96155
96987
  done,
96156
96988
  "/",
@@ -96158,11 +96990,10 @@ function TodoChecklist({ todos }) {
96158
96990
  ")"
96159
96991
  ] })
96160
96992
  ] }),
96161
- todos.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
96993
+ todos.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
96162
96994
  Text,
96163
96995
  {
96164
- color: t.status === "in_progress" ? "cyan" : void 0,
96165
- dimColor: t.status === "completed",
96996
+ ...t.status === "in_progress" ? paint("action") : t.status === "completed" ? paint("secondary") : {},
96166
96997
  bold: t.status === "in_progress",
96167
96998
  strikethrough: t.status === "completed",
96168
96999
  children: [
@@ -96176,35 +97007,17 @@ function TodoChecklist({ todos }) {
96176
97007
  ))
96177
97008
  ] });
96178
97009
  }
96179
- var import_jsx_runtime6, TODO_MARKS;
97010
+ var import_jsx_runtime8, TODO_MARKS;
96180
97011
  var init_TodoChecklist = __esm({
96181
97012
  async "src/components/TodoChecklist.tsx"() {
96182
97013
  "use strict";
96183
97014
  await init_build2();
96184
- import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
97015
+ init_theme();
97016
+ import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
96185
97017
  TODO_MARKS = { pending: "\u2610", in_progress: "\u25D0", completed: "\u2611" };
96186
97018
  }
96187
97019
  });
96188
97020
 
96189
- // src/render/mode-line.ts
96190
- function modeLine(mode) {
96191
- switch (mode) {
96192
- case "default":
96193
- return { text: "? default mode (shift+tab to cycle)" };
96194
- case "accept-edits":
96195
- return { text: "\u23F5\u23F5 accept edits on (shift+tab to cycle)", color: "green" };
96196
- case "plan":
96197
- return { text: "\u23F8 plan mode, read-only (shift+tab to cycle)", color: "blue" };
96198
- case "yolo":
96199
- return { text: "\u26A0 yolo, no prompts (shift+tab to cycle)", color: "red" };
96200
- }
96201
- }
96202
- var init_mode_line = __esm({
96203
- "src/render/mode-line.ts"() {
96204
- "use strict";
96205
- }
96206
- });
96207
-
96208
97021
  // src/render/footer.ts
96209
97022
  function estimateCostUsd(inputTokens, outputTokens) {
96210
97023
  return (inputTokens * ESTIMATED_USD_PER_MTOK.input + outputTokens * ESTIMATED_USD_PER_MTOK.output) / 1e6;
@@ -96222,31 +97035,45 @@ function contextPercent(usedTokens, maxTokens) {
96222
97035
  function contextLevel(pct) {
96223
97036
  return pct >= CONTEXT_CRITICAL_PCT ? "critical" : pct >= CONTEXT_WARN_PCT ? "warn" : "ok";
96224
97037
  }
97038
+ function contextBar(pct, unicode = true) {
97039
+ const filled = Math.max(0, Math.min(BAR_CELLS, Math.round(pct / 100 * BAR_CELLS)));
97040
+ return unicode ? "\u25B0".repeat(filled) + "\u25B1".repeat(BAR_CELLS - filled) : `[${"#".repeat(filled)}${"-".repeat(BAR_CELLS - filled)}]`;
97041
+ }
97042
+ function formatModel(model) {
97043
+ return model === void 0 ? MODEL_PLACEHOLDER : `${model} (selected)`;
97044
+ }
96225
97045
  function formatFooter({ mode, contextPct, costUsd, model, columns }) {
96226
97046
  const level = contextLevel(contextPct);
96227
- const ctx = `ctx ${contextPct}%${level === "critical" ? " !!" : level === "warn" ? " !" : ""}`;
96228
- const fields = [modeLine(mode).text, ctx, formatCost(costUsd), model ?? MODEL_PLACEHOLDER];
97047
+ const unicode = activeGlyphs().rail === "\u2502";
97048
+ const mark = level === "critical" ? " !!" : level === "warn" ? " !" : "";
97049
+ const ctx = columns >= BAR_MIN_COLUMNS ? `ctx ${contextBar(contextPct, unicode)} ${contextPct}%${mark}` : `ctx ${contextPct}%${mark}`;
97050
+ const fields = [modeLine(mode, unicode).text, ctx, formatCost(costUsd), formatModel(model)];
96229
97051
  while (fields.length > 1 && fields.join(" ").length > columns) fields.pop();
96230
97052
  return { text: fields.join(" "), level };
96231
97053
  }
96232
- function formatTurnSummary({ toolCount, elapsedMs, inputTokens, outputTokens, interrupted, failed }) {
97054
+ function formatTurnSummary({ toolCount, elapsedMs, inputTokens, outputTokens, interrupted, failed }, unicode = true) {
96233
97055
  const tools = `${toolCount} tool${toolCount === 1 ? "" : "s"}`;
96234
97056
  const secs = (elapsedMs / 1e3).toFixed(1);
96235
- const head = interrupted ? `\u2717 Interrupted (${tools}, ${secs}s)` : failed ? `\u2717 Failed (${tools}, ${secs}s)` : `\u2713 Done (${tools}, ${secs}s)`;
96236
- return `${head} \u2502 ${formatTokens(inputTokens)} \u2192 ${formatTokens(outputTokens)} \u2502 ${formatCost(estimateCostUsd(inputTokens, outputTokens))}`;
97057
+ const [ok, bad, bar, arrow] = unicode ? ["\u2713", "\u2717", "\u2502", "\u2192"] : ["+", "x", "|", "->"];
97058
+ const head = interrupted ? `${bad} Interrupted (${tools}, ${secs}s)` : failed ? `${bad} Failed (${tools}, ${secs}s)` : `${ok} Done (${tools}, ${secs}s)`;
97059
+ return `${head} ${bar} ${formatTokens(inputTokens)} ${arrow} ${formatTokens(outputTokens)} ${bar} ${formatCost(estimateCostUsd(inputTokens, outputTokens))}`;
96237
97060
  }
96238
- function formatThinking(elapsedMs, approxTokens) {
96239
- return `\u273B Thinking\u2026 (${Math.floor(elapsedMs / 1e3)}s \xB7 ~${formatTokens(approxTokens)} tokens \xB7 esc to interrupt)`;
97061
+ function formatThinking(elapsedMs, approxTokens, unicode = true) {
97062
+ const [mark, dots, sep2] = unicode ? ["\u273B", "\u2026", "\xB7"] : ["*", "...", "-"];
97063
+ return `${mark} Thinking${dots} (${Math.floor(elapsedMs / 1e3)}s ${sep2} ~${formatTokens(approxTokens)} tokens ${sep2} esc to interrupt)`;
96240
97064
  }
96241
- var ESTIMATED_USD_PER_MTOK, MODEL_PLACEHOLDER, CONTEXT_WARN_PCT, CONTEXT_CRITICAL_PCT;
97065
+ var ESTIMATED_USD_PER_MTOK, MODEL_PLACEHOLDER, CONTEXT_WARN_PCT, CONTEXT_CRITICAL_PCT, BAR_CELLS, BAR_MIN_COLUMNS;
96242
97066
  var init_footer = __esm({
96243
97067
  "src/render/footer.ts"() {
96244
97068
  "use strict";
96245
97069
  init_mode_line();
97070
+ init_rail();
96246
97071
  ESTIMATED_USD_PER_MTOK = { input: 0.5, output: 1.5 };
96247
97072
  MODEL_PLACEHOLDER = "gateway default";
96248
97073
  CONTEXT_WARN_PCT = 80;
96249
97074
  CONTEXT_CRITICAL_PCT = 95;
97075
+ BAR_CELLS = 8;
97076
+ BAR_MIN_COLUMNS = 60;
96250
97077
  }
96251
97078
  });
96252
97079
 
@@ -96449,17 +97276,17 @@ function isSubsequence(needle, hay) {
96449
97276
  for (let j2 = 0; j2 < hay.length && i < needle.length; j2++) if (hay[j2] === needle[i]) i++;
96450
97277
  return i === needle.length;
96451
97278
  }
96452
- function tier(path10, q2) {
96453
- const base = path10.slice(path10.lastIndexOf("/") + 1);
97279
+ function tier(path11, q2) {
97280
+ const base = path11.slice(path11.lastIndexOf("/") + 1);
96454
97281
  if (base.startsWith(q2)) return 0;
96455
97282
  if (base.includes(q2)) return 1;
96456
- if (path10.includes(q2)) return 2;
97283
+ if (path11.includes(q2)) return 2;
96457
97284
  return 3;
96458
97285
  }
96459
97286
  function rankPaths(paths, query, limit = 8) {
96460
97287
  if (query === "") return paths.slice(0, limit);
96461
97288
  const q2 = query.toLowerCase();
96462
- return paths.map((path10) => ({ path: path10, lower: path10.toLowerCase() })).filter(({ lower }) => isSubsequence(q2, lower)).map(({ path: path10, lower }) => ({ path: path10, rank: tier(lower, q2) })).sort((a, b2) => a.rank - b2.rank || a.path.length - b2.path.length || (a.path < b2.path ? -1 : a.path > b2.path ? 1 : 0)).slice(0, limit).map((m2) => m2.path);
97289
+ return paths.map((path11) => ({ path: path11, lower: path11.toLowerCase() })).filter(({ lower }) => isSubsequence(q2, lower)).map(({ path: path11, lower }) => ({ path: path11, rank: tier(lower, q2) })).sort((a, b2) => a.rank - b2.rank || a.path.length - b2.path.length || (a.path < b2.path ? -1 : a.path > b2.path ? 1 : 0)).slice(0, limit).map((m2) => m2.path);
96463
97290
  }
96464
97291
  var init_mention_rank = __esm({
96465
97292
  "src/editor/mention-rank.ts"() {
@@ -96476,9 +97303,9 @@ function activeMention(text, cursor) {
96476
97303
  const explicit = token[1] === "!";
96477
97304
  return { start, query: token.slice(explicit ? 2 : 1), explicit };
96478
97305
  }
96479
- function insertMention(text, cursor, mention, path10) {
96480
- if (/\s/.test(path10)) return { text, cursor };
96481
- const inserted = `@${mention.explicit ? "!" : ""}${path10}`;
97306
+ function insertMention(text, cursor, mention, path11) {
97307
+ if (/\s/.test(path11)) return { text, cursor };
97308
+ const inserted = `@${mention.explicit ? "!" : ""}${path11}`;
96482
97309
  const head = text.slice(0, mention.start) + inserted;
96483
97310
  const next = text[cursor];
96484
97311
  if (next !== void 0 && /\s/.test(next)) return { text: head + text.slice(cursor), cursor: head.length + 1 };
@@ -96524,7 +97351,16 @@ function isKnownSlashCommand(name) {
96524
97351
  function formatHelp() {
96525
97352
  const width = Math.max(...SLASH_COMMANDS.map((c) => c.name.length));
96526
97353
  const lines2 = SLASH_COMMANDS.map((c) => ` /${c.name.padEnd(width)} ${c.description}`);
96527
- return ["Commands:", ...lines2, "", " @path attach a file to your message (@!path overrides the secret/.gitignore filters)", " ! on an empty prompt: shell mode, runs a command on this machine (not sent to the model)", "", "Press Esc while the agent is running to cancel the turn."].join("\n");
97354
+ return [
97355
+ "Commands:",
97356
+ ...lines2,
97357
+ "",
97358
+ " tab (no menu open) focus a step of the running turn: \u2191\u2193 move, enter opens its output, esc leaves; ctrl+o or /turns browses finished turns",
97359
+ " @path attach a file to your message (@!path overrides the secret/.gitignore filters)",
97360
+ " ! on an empty prompt: shell mode, runs a command on this machine (not sent to the model)",
97361
+ "",
97362
+ "Press Esc while the agent is running to cancel the turn."
97363
+ ].join("\n");
96528
97364
  }
96529
97365
  var SLASH_COMMANDS;
96530
97366
  var init_slash_commands = __esm({
@@ -96539,6 +97375,7 @@ var init_slash_commands = __esm({
96539
97375
  { name: "status", description: "Show project, session, mode, context and cost" },
96540
97376
  { name: "compact", description: "Summarize the conversation to free up context" },
96541
97377
  { name: "model", description: "List gateway models; /model <n|id> switches for this session" },
97378
+ { name: "turns", description: "Browse earlier turns and open their steps (shortcut: ctrl+o)" },
96542
97379
  { name: "tools", description: "List the tools available to the agent" },
96543
97380
  { name: "exit", description: "Quit the REPL (alias: /quit)" },
96544
97381
  { name: "quit", description: "Quit the REPL" }
@@ -96555,41 +97392,42 @@ function validateSubmission(text) {
96555
97392
  function withCursor(row, col) {
96556
97393
  return `${row.slice(0, col)}\x1B[7m${row[col] ?? " "}\x1B[27m${row.slice(col + 1)}`;
96557
97394
  }
96558
- function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
97395
+ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTab, locked = false, promptGlyph: promptGlyph2 = ">", onEmptyEnter, unicode = true }) {
96559
97396
  const { exit } = use_app_default();
97397
+ const { columns } = useTerminalSize();
96560
97398
  const { isRawModeSupported } = use_stdin_default();
96561
- const [buf, setBuf] = (0, import_react27.useState)(() => createBuffer());
96562
- const [hint, setHint] = (0, import_react27.useState)(null);
96563
- const bufRef = (0, import_react27.useRef)(buf);
96564
- const queue = (0, import_react27.useRef)([]);
96565
- const [files, setFiles] = (0, import_react27.useState)(null);
96566
- const filesRef = (0, import_react27.useRef)(null);
96567
- const loadingFiles = (0, import_react27.useRef)(false);
96568
- const [sel, setSel] = (0, import_react27.useState)(0);
96569
- const selRef = (0, import_react27.useRef)(0);
96570
- const [shell, setShellState] = (0, import_react27.useState)(false);
96571
- const shellRef = (0, import_react27.useRef)(false);
97399
+ const [buf, setBuf] = (0, import_react29.useState)(() => createBuffer());
97400
+ const [hint, setHint] = (0, import_react29.useState)(null);
97401
+ const bufRef = (0, import_react29.useRef)(buf);
97402
+ const queue = (0, import_react29.useRef)([]);
97403
+ const [files, setFiles] = (0, import_react29.useState)(null);
97404
+ const filesRef = (0, import_react29.useRef)(null);
97405
+ const loadingFiles = (0, import_react29.useRef)(false);
97406
+ const [sel, setSel] = (0, import_react29.useState)(0);
97407
+ const selRef = (0, import_react29.useRef)(0);
97408
+ const [shell, setShellState] = (0, import_react29.useState)(false);
97409
+ const shellRef = (0, import_react29.useRef)(false);
96572
97410
  const setShell = (on) => {
96573
97411
  shellRef.current = on;
96574
97412
  setShellState(on);
96575
97413
  };
96576
97414
  bufRef.current = buf;
96577
- const fail = (0, import_react27.useCallback)((what, err) => {
97415
+ const fail = (0, import_react29.useCallback)((what, err) => {
96578
97416
  setHint(`${what}: ${err instanceof Error ? err.message : String(err)}`);
96579
97417
  }, []);
96580
- (0, import_react27.useEffect)(() => {
97418
+ (0, import_react29.useEffect)(() => {
96581
97419
  history?.load().catch((err) => fail("Could not load prompt history", err));
96582
97420
  }, [history, fail]);
96583
- const dispatch = (0, import_react27.useCallback)((text) => {
97421
+ const dispatch = (0, import_react29.useCallback)((text) => {
96584
97422
  const result = onSubmit(text);
96585
97423
  if (result instanceof Promise) result.catch((err) => fail("Submit failed", err));
96586
97424
  }, [onSubmit, fail]);
96587
- (0, import_react27.useEffect)(() => {
97425
+ (0, import_react29.useEffect)(() => {
96588
97426
  if (!disabled && queue.current.length > 0) dispatch(queue.current.shift());
96589
97427
  }, [disabled, dispatch]);
96590
97428
  const mention = shell ? null : activeMention(buf.text, buf.cursor);
96591
97429
  const mentionOpen = mention !== null;
96592
- (0, import_react27.useEffect)(() => {
97430
+ (0, import_react29.useEffect)(() => {
96593
97431
  if (!mentionOpen || !listFiles2 || filesRef.current !== null || loadingFiles.current) return;
96594
97432
  loadingFiles.current = true;
96595
97433
  listFiles2().then((f) => {
@@ -96659,6 +97497,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
96659
97497
  case "tab": {
96660
97498
  const completed = completeSlash(cur.text, slashMatches(cur.text, SLASH_COMMANDS));
96661
97499
  if (completed) update(createBuffer(completed));
97500
+ else onTab?.();
96662
97501
  return;
96663
97502
  }
96664
97503
  case "historyPrev":
@@ -96685,6 +97524,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
96685
97524
  const typed = cur.text.trim();
96686
97525
  if (typed === "") {
96687
97526
  if (inShell) setHint("Type a command after ! (Backspace on an empty prompt leaves shell mode)");
97527
+ else onEmptyEnter?.();
96688
97528
  return;
96689
97529
  }
96690
97530
  const text = inShell ? `!${typed}` : typed;
@@ -96702,36 +97542,39 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
96702
97542
  else dispatch(text);
96703
97543
  }
96704
97544
  }
96705
- }, { isActive: Boolean(isRawModeSupported) });
97545
+ }, { isActive: Boolean(isRawModeSupported) && !locked });
96706
97546
  if (!isRawModeSupported) {
96707
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "red", children: 'Interactive input needs a TTY (stdin is not a terminal). Use `spacecode -p "<prompt>"` for headless runs.' });
97547
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...paint("delete"), children: 'Interactive input needs a TTY (stdin is not a terminal). Use `spacecode -p "<prompt>"` for headless runs.' });
96708
97548
  }
96709
- if (disabled && buf.text === "") return null;
96710
97549
  const rows = lines(buf);
96711
97550
  const { line, col } = lineCol(buf);
96712
97551
  const matches = shell ? [] : slashMatches(buf.text, SLASH_COMMANDS);
96713
97552
  const picks = mention && files ? rankPaths(files, mention.query, PICKER_ROWS) : [];
96714
97553
  const pickAt = Math.min(sel, Math.max(picks.length - 1, 0));
96715
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", children: [
96716
- rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96717
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: shell ? "magenta" : "green", bold: true, dimColor: disabled, children: i === 0 ? shell ? "! " : "> " : " " }),
96718
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: disabled, children: i === line && !disabled ? withCursor(row, col) : row })
96719
- ] }, i)),
96720
- matches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { dimColor: true, children: [
96721
- matches.map((c) => `/${c.name} \u2014 ${c.description}`).join(" "),
97554
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", children: [
97555
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { flexDirection: "column", borderStyle: unicode ? "round" : "classic", borderColor: paint("secondary").color, borderDimColor: disabled, paddingX: 1, width: Math.min(columns, MAX_STATIC_LINE), children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
97556
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...paint(shell ? "action" : "agent"), bold: true, dimColor: disabled, children: i === 0 ? shell ? "! " : `${promptGlyph2} ` : " " }),
97557
+ disabled && !locked && buf.text === "" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...paint("secondary"), children: "turn running - type to queue your next prompt" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { dimColor: disabled, children: i === line && !disabled ? withCursor(row, col) : row })
97558
+ ] }, i)) }),
97559
+ matches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { ...paint("secondary"), children: [
97560
+ matches.map((c) => `/${c.name} ${unicode ? "\u2014" : "-"} ${c.description}`).join(" "),
96722
97561
  " (Tab to complete)"
96723
97562
  ] }),
96724
- shell && buf.text === "" && !disabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: "shell mode: runs on your machine, not sent to the model (Backspace exits)" }),
96725
- picks.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: i === pickAt ? "cyan" : void 0, dimColor: i !== pickAt, children: `${i === pickAt ? "\u203A" : " "} ${p}` }, p)),
96726
- picks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: "(\u2191/\u2193 select, Tab or Enter to insert)" }),
96727
- hint && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "red", children: hint })
97563
+ shell && buf.text === "" && !disabled && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...paint("secondary"), children: "shell mode: runs on your machine, not sent to the model (Backspace exits)" }),
97564
+ picks.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...i === pickAt ? paint("action") : paint("secondary"), children: `${i === pickAt ? unicode ? "\u203A" : ">" : " "} ${p}` }, p)),
97565
+ picks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { ...paint("secondary"), children: [
97566
+ "(",
97567
+ unicode ? "\u2191/\u2193" : "up/down",
97568
+ " select, Tab or Enter to insert)"
97569
+ ] }),
97570
+ hint && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...paint("delete"), children: hint })
96728
97571
  ] });
96729
97572
  }
96730
- var import_react27, import_jsx_runtime7, PICKER_ROWS;
97573
+ var import_react29, import_jsx_runtime9, PICKER_ROWS;
96731
97574
  var init_PromptEditor = __esm({
96732
97575
  async "src/components/PromptEditor.tsx"() {
96733
97576
  "use strict";
96734
- import_react27 = __toESM(require_react(), 1);
97577
+ import_react29 = __toESM(require_react(), 1);
96735
97578
  await init_build2();
96736
97579
  init_edit_buffer();
96737
97580
  init_editor_keys();
@@ -96739,33 +97582,14 @@ var init_PromptEditor = __esm({
96739
97582
  init_mention_token();
96740
97583
  init_slash_menu();
96741
97584
  init_slash_commands();
96742
- import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
97585
+ init_theme();
97586
+ init_hints();
97587
+ await init_useTerminalSize();
97588
+ import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
96743
97589
  PICKER_ROWS = 6;
96744
97590
  }
96745
97591
  });
96746
97592
 
96747
- // src/editor/credential-filter.ts
96748
- function containsCredential(entry) {
96749
- return PATTERNS.some((re3) => re3.test(entry));
96750
- }
96751
- var PATTERNS;
96752
- var init_credential_filter = __esm({
96753
- "src/editor/credential-filter.ts"() {
96754
- "use strict";
96755
- PATTERNS = [
96756
- /\bsk[_-][A-Za-z0-9_-]{16,}/,
96757
- /\bgh[pos]_[A-Za-z0-9]{20,}/,
96758
- /\bgithub_pat_[A-Za-z0-9_]{20,}/,
96759
- /\bAKIA[0-9A-Z]{16}\b/,
96760
- /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}/,
96761
- /-----BEGIN [A-Z ]*PRIVATE KEY-----/,
96762
- /\bxox[abp]-[A-Za-z0-9-]{10,}/,
96763
- /\bnpm_[A-Za-z0-9]{30,}/,
96764
- /\b[A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*=\S{8,}/i
96765
- ];
96766
- }
96767
- });
96768
-
96769
97593
  // src/editor/history-store.ts
96770
97594
  var PromptHistory;
96771
97595
  var init_history_store = __esm({
@@ -96847,24 +97671,24 @@ var init_history_store = __esm({
96847
97671
  });
96848
97672
 
96849
97673
  // src/editor/history-file-io.ts
96850
- import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
96851
- import path from "node:path";
97674
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
97675
+ import path2 from "node:path";
96852
97676
  function createFileHistoryIO(file) {
96853
97677
  return {
96854
97678
  async read() {
96855
97679
  try {
96856
- return await readFile(file, "utf8");
97680
+ return await readFile2(file, "utf8");
96857
97681
  } catch (err) {
96858
97682
  if (err.code === "ENOENT") return null;
96859
97683
  throw err;
96860
97684
  }
96861
97685
  },
96862
97686
  async write(content) {
96863
- await mkdir(path.dirname(file), { recursive: true, mode: 448 });
97687
+ await mkdir2(path2.dirname(file), { recursive: true, mode: 448 });
96864
97688
  const tmp = `${file}.${process.pid}.tmp`;
96865
- await writeFile(tmp, content, { mode: 384 });
96866
- await chmod(tmp, 384);
96867
- await rename(tmp, file);
97689
+ await writeFile2(tmp, content, { mode: 384 });
97690
+ await chmod2(tmp, 384);
97691
+ await rename2(tmp, file);
96868
97692
  }
96869
97693
  };
96870
97694
  }
@@ -97310,6 +98134,7 @@ async function runAgentLoop(prompt, config, executor, emit, signal, existingMess
97310
98134
  }
97311
98135
  lastToolCount = toolUseBlocks.length;
97312
98136
  if (data.stop_reason !== "tool_use") {
98137
+ if (data.content.length > 0) messages.push({ role: "assistant", content: data.content });
97313
98138
  emitDone(emit, {
97314
98139
  type: "done",
97315
98140
  success: true,
@@ -97336,8 +98161,8 @@ async function runAgentLoop(prompt, config, executor, emit, signal, existingMess
97336
98161
  const toolInput = block.input ?? {};
97337
98162
  const startTime = Date.now();
97338
98163
  let result;
97339
- const onOutput = (id, stream, text) => {
97340
- emitToolOutput(emit, { type: "tool_output", id, stream, text });
98164
+ const onOutput = (_reportedId, stream, text) => {
98165
+ emitToolOutput(emit, { type: "tool_output", id: toolId, stream, text });
97341
98166
  };
97342
98167
  try {
97343
98168
  result = await executor.execute(toolName, toolInput, onOutput);
@@ -97539,9 +98364,9 @@ function createFileTools(store) {
97539
98364
  readCache.clear();
97540
98365
  listCache.clear();
97541
98366
  }
97542
- function invalidatePath(path10) {
97543
- readCache.delete(path10);
97544
- const parent = path10.includes("/") ? path10.slice(0, path10.lastIndexOf("/") + 1) : "";
98367
+ function invalidatePath(path11) {
98368
+ readCache.delete(path11);
98369
+ const parent = path11.includes("/") ? path11.slice(0, path11.lastIndexOf("/") + 1) : "";
97545
98370
  listCache.delete(parent);
97546
98371
  listCache.delete("");
97547
98372
  listCache.delete(".");
@@ -97549,36 +98374,36 @@ function createFileTools(store) {
97549
98374
  }
97550
98375
  const handlers = {
97551
98376
  read_file: async (input) => {
97552
- const path10 = input.path;
97553
- const cached = readCache.get(path10);
98377
+ const path11 = input.path;
98378
+ const cached = readCache.get(path11);
97554
98379
  if (cached !== void 0) {
97555
98380
  return { output: cached };
97556
98381
  }
97557
- const content = await store.readFile(path10);
98382
+ const content = await store.readFile(path11);
97558
98383
  if (content === null) {
97559
- return { output: `File not found: ${path10}`, is_error: true };
98384
+ return { output: `File not found: ${path11}`, is_error: true };
97560
98385
  }
97561
- readCache.set(path10, content);
98386
+ readCache.set(path11, content);
97562
98387
  return { output: content };
97563
98388
  },
97564
98389
  write_file: async (input) => {
97565
- const path10 = input.path;
98390
+ const path11 = input.path;
97566
98391
  const content = input.content;
97567
- await store.writeFile(path10, content);
97568
- invalidatePath(path10);
97569
- readCache.set(path10, content);
97570
- return { output: `Written: ${path10}` };
98392
+ await store.writeFile(path11, content);
98393
+ invalidatePath(path11);
98394
+ readCache.set(path11, content);
98395
+ return { output: `Written: ${path11}` };
97571
98396
  },
97572
98397
  edit_file: async (input) => {
97573
- const path10 = input.path;
98398
+ const path11 = input.path;
97574
98399
  const oldStr = input.old_string;
97575
98400
  const newStr = input.new_string;
97576
- let content = readCache.get(path10);
98401
+ let content = readCache.get(path11);
97577
98402
  if (content === void 0) {
97578
- const stored = await store.readFile(path10);
98403
+ const stored = await store.readFile(path11);
97579
98404
  if (stored === null) {
97580
98405
  return {
97581
- output: `File not found: ${path10}. Did you read the file first?`,
98406
+ output: `File not found: ${path11}. Did you read the file first?`,
97582
98407
  is_error: true
97583
98408
  };
97584
98409
  }
@@ -97588,41 +98413,41 @@ function createFileTools(store) {
97588
98413
  if (lineNumbers.length === 0) {
97589
98414
  const lineCount = content.split("\n").length;
97590
98415
  return {
97591
- output: `old_string not found in ${path10}. The file has ${lineCount} lines. Did you read the file first?`,
98416
+ output: `old_string not found in ${path11}. The file has ${lineCount} lines. Did you read the file first?`,
97592
98417
  is_error: true
97593
98418
  };
97594
98419
  }
97595
98420
  if (lineNumbers.length > 1) {
97596
98421
  const locations = lineNumbers.map((ln) => ` - line ${ln}`).join("\n");
97597
98422
  return {
97598
- output: `old_string matches ${lineNumbers.length} locations in ${path10}:
98423
+ output: `old_string matches ${lineNumbers.length} locations in ${path11}:
97599
98424
  ${locations}
97600
98425
  Include more surrounding context to make old_string unique.`,
97601
98426
  is_error: true
97602
98427
  };
97603
98428
  }
97604
- const result = await store.editFile(path10, oldStr, newStr);
98429
+ const result = await store.editFile(path11, oldStr, newStr);
97605
98430
  if (!result.success) {
97606
98431
  return { output: result.error, is_error: true };
97607
98432
  }
97608
- invalidatePath(path10);
97609
- const updated = await store.readFile(path10);
97610
- if (updated !== null) readCache.set(path10, updated);
97611
- return { output: `Edited: ${path10}` };
98433
+ invalidatePath(path11);
98434
+ const updated = await store.readFile(path11);
98435
+ if (updated !== null) readCache.set(path11, updated);
98436
+ return { output: `Edited: ${path11}` };
97612
98437
  },
97613
98438
  list_directory: async (input) => {
97614
- const path10 = input.path;
97615
- const cached = listCache.get(path10);
98439
+ const path11 = input.path;
98440
+ const cached = listCache.get(path11);
97616
98441
  if (cached !== void 0) {
97617
98442
  if (cached.length === 0) {
97618
- return { output: `Empty or not found: ${path10}` };
98443
+ return { output: `Empty or not found: ${path11}` };
97619
98444
  }
97620
98445
  return { output: cached.join("\n") };
97621
98446
  }
97622
- const entries = await store.listDirectory(path10);
97623
- listCache.set(path10, entries);
98447
+ const entries = await store.listDirectory(path11);
98448
+ listCache.set(path11, entries);
97624
98449
  if (entries.length === 0) {
97625
- return { output: `Empty or not found: ${path10}` };
98450
+ return { output: `Empty or not found: ${path11}` };
97626
98451
  }
97627
98452
  return { output: entries.join("\n") };
97628
98453
  }
@@ -97858,8 +98683,8 @@ function parseTodos(input) {
97858
98683
  if (typeof e.status !== "string" || !STATUSES.includes(e.status)) throw new Error(`Item ${i + 1}: "status" must be one of ${STATUSES.join(", ")}`);
97859
98684
  return { content: e.content.trim(), status: e.status };
97860
98685
  });
97861
- const active = items.filter((t) => t.status === "in_progress").length;
97862
- if (active > 1) throw new Error(`Only one item may be "in_progress" at a time (found ${active})`);
98686
+ const active2 = items.filter((t) => t.status === "in_progress").length;
98687
+ if (active2 > 1) throw new Error(`Only one item may be "in_progress" at a time (found ${active2})`);
97863
98688
  return items;
97864
98689
  }
97865
98690
  function createTodoTool(onUpdate) {
@@ -97958,9 +98783,9 @@ var init_host = __esm({
97958
98783
  });
97959
98784
 
97960
98785
  // ../agent/src/host/local-host.ts
97961
- import { execFile } from "node:child_process";
98786
+ import { execFile as execFile2 } from "node:child_process";
97962
98787
  import { promises as fs2 } from "node:fs";
97963
- import * as path2 from "node:path";
98788
+ import * as path3 from "node:path";
97964
98789
  var LocalHost;
97965
98790
  var init_local_host = __esm({
97966
98791
  "../agent/src/host/local-host.ts"() {
@@ -97970,7 +98795,7 @@ var init_local_host = __esm({
97970
98795
  files;
97971
98796
  root;
97972
98797
  constructor(rootDir) {
97973
- this.root = path2.resolve(rootDir);
98798
+ this.root = path3.resolve(rootDir);
97974
98799
  this.files = {
97975
98800
  readFile: (p) => this.readFile(p),
97976
98801
  writeFile: (p, c) => this.writeFile(p, c),
@@ -97979,12 +98804,12 @@ var init_local_host = __esm({
97979
98804
  };
97980
98805
  }
97981
98806
  static inside(root, abs) {
97982
- const rel = path2.relative(root, abs);
97983
- return !(rel === ".." || rel.startsWith(".." + path2.sep) || path2.isAbsolute(rel));
98807
+ const rel = path3.relative(root, abs);
98808
+ return !(rel === ".." || rel.startsWith(".." + path3.sep) || path3.isAbsolute(rel));
97984
98809
  }
97985
98810
  /** Lexical confinement plus a symlink-aware check of the deepest existing ancestor. */
97986
98811
  async resolve(p) {
97987
- const abs = path2.resolve(this.root, p);
98812
+ const abs = path3.resolve(this.root, p);
97988
98813
  if (!_LocalHost.inside(this.root, abs)) throw new Error(`Path escapes workspace root: ${p}`);
97989
98814
  const realRoot = await fs2.realpath(this.root);
97990
98815
  let probe = abs;
@@ -98002,7 +98827,7 @@ var init_local_host = __esm({
98002
98827
  throw e;
98003
98828
  });
98004
98829
  if (st?.isSymbolicLink()) throw new Error(`Path is a dangling symlink: ${p}`);
98005
- const parent = path2.dirname(probe);
98830
+ const parent = path3.dirname(probe);
98006
98831
  if (parent === probe) break;
98007
98832
  probe = parent;
98008
98833
  }
@@ -98019,7 +98844,7 @@ var init_local_host = __esm({
98019
98844
  }
98020
98845
  async writeFile(p, content) {
98021
98846
  const abs = await this.resolve(p);
98022
- await fs2.mkdir(path2.dirname(abs), { recursive: true });
98847
+ await fs2.mkdir(path3.dirname(abs), { recursive: true });
98023
98848
  await fs2.writeFile(abs, content, "utf8");
98024
98849
  }
98025
98850
  async editFile(p, oldStr, newStr) {
@@ -98049,7 +98874,7 @@ var init_local_host = __esm({
98049
98874
  */
98050
98875
  exec(command, onOutput) {
98051
98876
  return new Promise((resolve2, reject) => {
98052
- const child = execFile(
98877
+ const child = execFile2(
98053
98878
  "/bin/sh",
98054
98879
  ["-c", command],
98055
98880
  { cwd: this.root, maxBuffer: 10 * 1024 * 1024 },
@@ -98311,7 +99136,7 @@ var init_policy = __esm({
98311
99136
 
98312
99137
  // ../agent/src/host/audit-log.ts
98313
99138
  import { promises as fs3 } from "node:fs";
98314
- import * as path3 from "node:path";
99139
+ import * as path4 from "node:path";
98315
99140
  function truncate(e) {
98316
99141
  return e.subject.length > MAX_SUBJECT ? { ...e, subject: e.subject.slice(0, MAX_SUBJECT) + "...[truncated]" } : e;
98317
99142
  }
@@ -98325,7 +99150,7 @@ var init_audit_log = __esm({
98325
99150
  this.file = file;
98326
99151
  }
98327
99152
  async record(entry) {
98328
- await fs3.mkdir(path3.dirname(this.file), { recursive: true });
99153
+ await fs3.mkdir(path4.dirname(this.file), { recursive: true });
98329
99154
  await fs3.appendFile(this.file, JSON.stringify(truncate(entry)) + "\n", { mode: 384 });
98330
99155
  }
98331
99156
  };
@@ -98570,136 +99395,39 @@ var init_src2 = __esm({
98570
99395
  }
98571
99396
  });
98572
99397
 
98573
- // src/session/session-store.ts
98574
- import { chmod as chmod2, mkdir as mkdir2, readdir, readFile as readFile2, rename as rename2, rm, writeFile as writeFile2 } from "node:fs/promises";
98575
- import { randomBytes } from "node:crypto";
98576
- import path4 from "node:path";
98577
- function redactCredentials(value) {
98578
- if (typeof value === "string") return containsCredential(value) ? REDACTED : value;
98579
- if (Array.isArray(value)) return value.map(redactCredentials);
98580
- if (value && typeof value === "object") {
98581
- return Object.fromEntries(Object.entries(value).map(([k2, v2]) => [k2, redactCredentials(v2)]));
98582
- }
98583
- return value;
98584
- }
98585
- function parseSession(file, text) {
98586
- const lines2 = text.split("\n").filter((l) => l.length > 0);
98587
- if (lines2.length === 0) throw new SessionError(file, "Session file is empty");
98588
- let parsed;
98589
- try {
98590
- parsed = lines2.map((l) => JSON.parse(l));
98591
- } catch (err) {
98592
- throw new SessionError(file, `Corrupt session file: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
98593
- }
98594
- const meta = parsed[0];
98595
- if (meta.v !== 1 || typeof meta.id !== "string" || typeof meta.projectRoot !== "string" || typeof meta.updatedAt !== "string") {
98596
- throw new SessionError(file, "Unrecognized session header (expected v1 with id, projectRoot, updatedAt)");
98597
- }
98598
- const messages = parsed.slice(1);
98599
- if (!messages.every((m2) => m2 && (m2.role === "user" || m2.role === "assistant"))) {
98600
- throw new SessionError(file, "Corrupt session file: a message has no valid role");
98601
- }
98602
- return { meta, messages };
98603
- }
98604
- function formatAge(fromIso, now) {
98605
- const secs = Math.max(0, Math.floor((now.getTime() - new Date(fromIso).getTime()) / 1e3));
98606
- if (secs < 60) return `${secs}s ago`;
98607
- if (secs < 3600) return `${Math.floor(secs / 60)}m ago`;
98608
- if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`;
98609
- return `${Math.floor(secs / 86400)}d ago`;
98610
- }
98611
- function titleFromMessages(messages) {
98612
- const first = messages.find((m2) => m2.role === "user");
98613
- const text = !first ? "" : typeof first.content === "string" ? first.content : first.content.map((b2) => b2.text ?? "").join(" ");
98614
- const oneLine = text.replace(/\s+/g, " ").trim();
98615
- return oneLine.length > 60 ? `${oneLine.slice(0, 59)}\u2026` : oneLine || "(untitled)";
98616
- }
98617
- var MAX_SESSION_BYTES, REDACTED, SessionError, ID, SessionStore;
98618
- var init_session_store = __esm({
98619
- "src/session/session-store.ts"() {
98620
- "use strict";
98621
- init_credential_filter();
98622
- MAX_SESSION_BYTES = 5 * 1024 * 1024;
98623
- REDACTED = "[redacted: credential-shaped content]";
98624
- SessionError = class extends Error {
98625
- constructor(file, message, options) {
98626
- super(`${message} (${file})`, options);
98627
- this.file = file;
98628
- this.name = "SessionError";
98629
- }
98630
- };
98631
- ID = /^[A-Za-z0-9_-]{8,64}$/;
98632
- SessionStore = class {
98633
- constructor(dir, now = () => /* @__PURE__ */ new Date()) {
98634
- this.dir = dir;
98635
- this.now = now;
98636
- }
98637
- newId() {
98638
- return `${this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomBytes(4).toString("hex")}`;
98639
- }
98640
- fileFor(id) {
98641
- if (!ID.test(id)) throw new SessionError(this.dir, `Invalid session id ${JSON.stringify(id)}`);
98642
- return path4.join(this.dir, `${id}.jsonl`);
98643
- }
98644
- async save(session) {
98645
- const file = this.fileFor(session.meta.id);
98646
- const meta = { ...session.meta, updatedAt: this.now().toISOString() };
98647
- const body = [meta, ...session.messages].map((entry) => JSON.stringify(redactCredentials(entry))).join("\n") + "\n";
98648
- const bytes = Buffer.byteLength(body);
98649
- if (bytes > MAX_SESSION_BYTES) {
98650
- throw new SessionError(file, `Session is ${bytes} bytes, over the ${MAX_SESSION_BYTES}-byte cap; use /compact or /clear`);
98651
- }
98652
- const tmp = `${file}.${process.pid}.tmp`;
98653
- try {
98654
- await mkdir2(this.dir, { recursive: true, mode: 448 });
98655
- await chmod2(this.dir, 448);
98656
- await writeFile2(tmp, body, { mode: 384 });
98657
- await chmod2(tmp, 384);
98658
- await rename2(tmp, file);
98659
- } catch (err) {
98660
- throw new SessionError(file, `Could not save session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
98661
- }
98662
- }
98663
- async load(id) {
98664
- const file = this.fileFor(id);
98665
- let text;
98666
- try {
98667
- text = await readFile2(file, "utf8");
98668
- } catch (err) {
98669
- throw new SessionError(file, `Could not read session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
98670
- }
98671
- return parseSession(file, text);
99398
+ // src/session/replay.ts
99399
+ function replayTurns(messages) {
99400
+ const turns = [];
99401
+ let current = null;
99402
+ let seq = 0;
99403
+ const push = (event) => current?.events.push({ ...event, seq: ++seq });
99404
+ for (const message of messages) {
99405
+ const blocks = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
99406
+ if (message.role === "user") {
99407
+ const text = blocks.filter((b2) => b2.type === "text").map((b2) => b2.text ?? "").join("\n");
99408
+ if (text !== "" || !current) {
99409
+ current = { id: -(turns.length + 1), prompt: text || "(no prompt text)", events: [], interrupted: false };
99410
+ turns.push(current);
98672
99411
  }
98673
- /** Newest first. Unreadable files are reported, not skipped silently. */
98674
- async list() {
98675
- let names;
98676
- try {
98677
- names = await readdir(this.dir);
98678
- } catch (err) {
98679
- if (err.code === "ENOENT") return { sessions: [], errors: [] };
98680
- throw new SessionError(this.dir, `Could not list sessions: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
99412
+ for (const b2 of blocks) {
99413
+ if (b2.type === "tool_result" && b2.tool_use_id) {
99414
+ push({ type: "tool_result", id: b2.tool_use_id, output: contentText(b2.content), is_error: b2.is_error ?? false });
98681
99415
  }
98682
- const sessions = [];
98683
- const errors = [];
98684
- for (const name of names.filter((n) => n.endsWith(".jsonl"))) {
98685
- try {
98686
- sessions.push((await this.load(name.slice(0, -".jsonl".length))).meta);
98687
- } catch (err) {
98688
- errors.push(err instanceof SessionError ? err : new SessionError(path4.join(this.dir, name), String(err), { cause: err }));
98689
- }
98690
- }
98691
- sessions.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
98692
- return { sessions, errors };
98693
99416
  }
98694
- async delete(id) {
98695
- const file = this.fileFor(id);
98696
- try {
98697
- await rm(file);
98698
- } catch (err) {
98699
- throw new SessionError(file, `Could not delete session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
98700
- }
99417
+ } else if (current) {
99418
+ for (const b2 of blocks) {
99419
+ if (b2.type === "text" && b2.text) push({ type: "text", content: b2.text });
99420
+ else if (b2.type === "tool_use" && b2.id && b2.name) push({ type: "tool_call", id: b2.id, tool: b2.name, input: b2.input ?? {} });
98701
99421
  }
98702
- };
99422
+ }
99423
+ }
99424
+ return turns;
99425
+ }
99426
+ var contentText;
99427
+ var init_replay = __esm({
99428
+ "src/session/replay.ts"() {
99429
+ "use strict";
99430
+ contentText = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.map((b2) => b2 && typeof b2 === "object" && "text" in b2 ? String(b2.text ?? "") : "").join("") : "";
98703
99431
  }
98704
99432
  });
98705
99433
 
@@ -99090,7 +99818,7 @@ var init_mention_resolver = __esm({
99090
99818
  });
99091
99819
 
99092
99820
  // src/editor/node-mention-fs.ts
99093
- import { open, realpath, stat } from "node:fs/promises";
99821
+ import { open as open2, realpath, stat as stat2 } from "node:fs/promises";
99094
99822
  import path6 from "node:path";
99095
99823
  function isInside(root, target) {
99096
99824
  return target === root || target.startsWith(root.endsWith(path6.sep) ? root : root + path6.sep);
@@ -99113,13 +99841,13 @@ function createNodeMentionFS(cwd2) {
99113
99841
  const real = await confined(abs);
99114
99842
  if (real === null) return null;
99115
99843
  if (real === "") return { size: 0, isFile: false };
99116
- const st = await stat(real);
99844
+ const st = await stat2(real);
99117
99845
  return { size: st.size, isFile: st.isFile() };
99118
99846
  },
99119
99847
  async readFile(abs, maxBytes) {
99120
99848
  const real = await confined(abs);
99121
99849
  if (!real) throw new Error(`refusing to read outside cwd: ${abs}`);
99122
- const fh = await open(real, "r");
99850
+ const fh = await open2(real, "r");
99123
99851
  try {
99124
99852
  const buf = Buffer.alloc(maxBytes);
99125
99853
  const { bytesRead } = await fh.read(buf, 0, maxBytes, 0);
@@ -99226,109 +99954,144 @@ var init_api_key = __esm({
99226
99954
  // src/useReplController.ts
99227
99955
  import { readFile as readFile4 } from "node:fs/promises";
99228
99956
  import { homedir as homedir2 } from "node:os";
99229
- function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2 }) {
99957
+ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
99230
99958
  const { exit } = use_app_default();
99231
- const mentions = (0, import_react28.useMemo)(() => mentionsOpt ?? createNodeMentions(process.cwd()), [mentionsOpt]);
99232
- const [events, setEvents] = (0, import_react28.useState)([]);
99233
- const [turns, setTurns] = (0, import_react28.useState)([]);
99234
- const turnIdRef = (0, import_react28.useRef)(0);
99235
- const [isRunning, setIsRunning] = (0, import_react28.useState)(false);
99236
- const [history, setHistory] = (0, import_react28.useState)([]);
99237
- const [turnTokens, setTurnTokens] = (0, import_react28.useState)(null);
99238
- const [sessionTokens, setSessionTokens] = (0, import_react28.useState)({ input: 0, output: 0 });
99239
- const [turnStartedAt, setTurnStartedAt] = (0, import_react28.useState)(null);
99240
- const [now, setNow] = (0, import_react28.useState)(() => Date.now());
99241
- (0, import_react28.useEffect)(() => {
99959
+ const mentions = (0, import_react30.useMemo)(() => mentionsOpt ?? createNodeMentions(process.cwd()), [mentionsOpt]);
99960
+ const [events, setEvents] = (0, import_react30.useState)([]);
99961
+ const [turns, setTurns] = (0, import_react30.useState)([]);
99962
+ const turnIdRef = (0, import_react30.useRef)(0);
99963
+ const [isRunning, setIsRunning] = (0, import_react30.useState)(false);
99964
+ const [livePrompt, setLivePrompt] = (0, import_react30.useState)(null);
99965
+ const [history, setHistory] = (0, import_react30.useState)([]);
99966
+ const [turnTokens, setTurnTokens] = (0, import_react30.useState)(null);
99967
+ const [sessionTokens, setSessionTokens] = (0, import_react30.useState)({ input: 0, output: 0 });
99968
+ const [turnStartedAt, setTurnStartedAt] = (0, import_react30.useState)(null);
99969
+ const [now, setNow] = (0, import_react30.useState)(() => Date.now());
99970
+ (0, import_react30.useEffect)(() => {
99242
99971
  if (turnStartedAt === null) return;
99243
99972
  setNow(Date.now());
99244
99973
  const timer = setInterval(() => setNow(Date.now()), 1e3);
99245
99974
  return () => clearInterval(timer);
99246
99975
  }, [turnStartedAt]);
99247
- const projectRoot = (0, import_react28.useMemo)(() => process.cwd(), []);
99248
- const sessionIdRef = (0, import_react28.useRef)(null);
99249
- const sessionCreatedAtRef = (0, import_react28.useRef)(null);
99250
- const sessionTokensRef = (0, import_react28.useRef)({ input: 0, output: 0 });
99251
- const resumeListRef = (0, import_react28.useRef)([]);
99252
- const [model, setModel] = (0, import_react28.useState)(null);
99253
- const [todos, setTodos] = (0, import_react28.useState)([]);
99254
- const [notices, setNotices] = (0, import_react28.useState)([]);
99255
- const [pending, setPending] = (0, import_react28.useState)(null);
99256
- const abortRef = (0, import_react28.useRef)(null);
99257
- const pendingRef = (0, import_react28.useRef)(null);
99258
- const toolsetRef = (0, import_react28.useRef)(null);
99259
- (0, import_react28.useEffect)(() => {
99976
+ const projectRoot = (0, import_react30.useMemo)(() => process.cwd(), []);
99977
+ const sessionIdRef = (0, import_react30.useRef)(null);
99978
+ const sessionCreatedAtRef = (0, import_react30.useRef)(null);
99979
+ const sessionTokensRef = (0, import_react30.useRef)({ input: 0, output: 0 });
99980
+ const resumeListRef = (0, import_react30.useRef)([]);
99981
+ const [model, setModel] = (0, import_react30.useState)(null);
99982
+ const [todos, setTodosState] = (0, import_react30.useState)([]);
99983
+ const todosRef = (0, import_react30.useRef)([]);
99984
+ const setTodos = (0, import_react30.useCallback)((t) => {
99985
+ todosRef.current = t;
99986
+ setTodosState(t);
99987
+ }, []);
99988
+ const [replayed, setReplayed] = (0, import_react30.useState)([]);
99989
+ const [notices, setNotices] = (0, import_react30.useState)([]);
99990
+ const retryRef = (0, import_react30.useRef)(null);
99991
+ const [pending, setPending] = (0, import_react30.useState)(null);
99992
+ const abortRef = (0, import_react30.useRef)(null);
99993
+ const pendingRef = (0, import_react30.useRef)(null);
99994
+ const toolsetRef = (0, import_react30.useRef)(null);
99995
+ (0, import_react30.useEffect)(() => {
99260
99996
  sessionTokensRef.current = sessionTokens;
99261
99997
  }, [sessionTokens]);
99262
- const [permissions] = (0, import_react28.useState)(() => new PermissionState(yolo2 ? "yolo" : "default"));
99263
- const [mode, setMode] = (0, import_react28.useState)(permissions.mode);
99264
- (0, import_react28.useEffect)(() => permissions.onChange(setMode), [permissions]);
99265
- const answeredThisKeypressRef = (0, import_react28.useRef)(false);
99266
- const setPendingConfirm = (0, import_react28.useCallback)((p) => {
99267
- pendingRef.current = p;
99268
- setPending(p);
99998
+ const [permissions] = (0, import_react30.useState)(() => new PermissionState(yolo2 ? "yolo" : "default"));
99999
+ const [mode, setMode] = (0, import_react30.useState)(permissions.mode);
100000
+ (0, import_react30.useEffect)(() => permissions.onChange(setMode), [permissions]);
100001
+ const answeredThisKeypressRef = (0, import_react30.useRef)(false);
100002
+ const confirmQueueRef = (0, import_react30.useRef)([]);
100003
+ const confirmIdRef = (0, import_react30.useRef)(0);
100004
+ const showNextConfirm = (0, import_react30.useCallback)(() => {
100005
+ const head = confirmQueueRef.current[0] ?? null;
100006
+ pendingRef.current = head;
100007
+ setPending(head);
100008
+ }, []);
100009
+ const enqueueConfirm = (0, import_react30.useCallback)((p) => {
100010
+ confirmQueueRef.current.push(p);
100011
+ if (confirmQueueRef.current.length === 1) showNextConfirm();
100012
+ }, [showNextConfirm]);
100013
+ const dequeueConfirm = (0, import_react30.useCallback)((p) => {
100014
+ confirmQueueRef.current = confirmQueueRef.current.filter((q2) => q2 !== p);
100015
+ showNextConfirm();
100016
+ }, [showNextConfirm]);
100017
+ const refuseAllConfirms = (0, import_react30.useCallback)(() => {
100018
+ const all = confirmQueueRef.current;
100019
+ confirmQueueRef.current = [];
100020
+ for (const p of all) p.resolve(false);
100021
+ pendingRef.current = null;
100022
+ setPending(null);
99269
100023
  }, []);
99270
- const commitTurn = (0, import_react28.useCallback)((prompt, turnEvents, interrupted, summary) => {
99271
- setTurns((prev) => [...prev, { id: turnIdRef.current++, prompt, events: turnEvents, interrupted, summary }]);
100024
+ const commitTurn = (0, import_react30.useCallback)((prompt, turnEvents, interrupted, summary, retained) => {
100025
+ setTurns((prev) => [...prev, { id: turnIdRef.current++, prompt, events: turnEvents, interrupted, summary, retained }]);
99272
100026
  }, []);
99273
- const notice = (0, import_react28.useCallback)((message) => {
100027
+ const notice = (0, import_react30.useCallback)((message) => {
99274
100028
  setNotices((prev) => [...prev, message]);
99275
100029
  }, []);
100030
+ const noticeError = (0, import_react30.useCallback)((view) => {
100031
+ setNotices((prev) => [...prev, { error: view }]);
100032
+ }, []);
99276
100033
  const { isRawModeSupported } = use_stdin_default();
99277
100034
  use_input_default((_input, key) => {
99278
100035
  if (key.shift && key.tab) {
99279
100036
  permissions.cycle();
99280
100037
  return;
99281
100038
  }
99282
- if (pendingRef.current || answeredThisKeypressRef.current) return;
100039
+ if (pendingRef.current || answeredThisKeypressRef.current || isEscOwned?.()) return;
99283
100040
  if (key.escape && abortRef.current) abortRef.current.abort();
99284
100041
  }, { isActive: Boolean(isRawModeSupported) });
99285
- const createToolset = (0, import_react28.useCallback)(
100042
+ const createToolset = (0, import_react30.useCallback)(
99286
100043
  () => createLocalToolset({
99287
100044
  cwd: process.cwd(),
99288
100045
  permissions,
99289
100046
  onTodos: setTodos,
99290
- confirm: (req) => new Promise(
99291
- (resolve2) => setPendingConfirm({
100047
+ confirm: (req) => new Promise((resolve2) => {
100048
+ const item = {
100049
+ id: confirmIdRef.current++,
99292
100050
  req,
99293
100051
  resolve: (decision) => {
99294
100052
  answeredThisKeypressRef.current = true;
99295
100053
  setTimeout(() => {
99296
100054
  answeredThisKeypressRef.current = false;
99297
100055
  }, 0);
99298
- setPendingConfirm(null);
100056
+ dequeueConfirm(item);
99299
100057
  resolve2(decision);
99300
100058
  }
99301
- })
99302
- )
100059
+ };
100060
+ enqueueConfirm(item);
100061
+ })
99303
100062
  }),
99304
- [permissions, setPendingConfirm]
100063
+ [permissions, enqueueConfirm, dequeueConfirm]
99305
100064
  );
99306
- const persist = (0, import_react28.useCallback)(
100065
+ const persist = (0, import_react30.useCallback)(
99307
100066
  async (messages, tokens) => {
99308
100067
  if (!sessions) return;
99309
100068
  try {
99310
100069
  const id = sessionIdRef.current ??= sessions.newId();
99311
100070
  const createdAt = sessionCreatedAtRef.current ??= (/* @__PURE__ */ new Date()).toISOString();
99312
- await sessions.save({ meta: { v: 1, id, projectRoot, createdAt, updatedAt: createdAt, tokens, title: titleFromMessages(messages) }, messages });
100071
+ await sessions.save({ meta: { v: 1, id, projectRoot, createdAt, updatedAt: createdAt, tokens, title: titleFromMessages(messages), turns: countTurns(messages), todos: todosRef.current }, messages });
99313
100072
  } catch (err) {
99314
- notice(`Session not saved: ${err instanceof Error ? err.message : String(err)}`);
100073
+ noticeError(describeError("session-save", err instanceof Error ? err.message : String(err)));
99315
100074
  }
99316
100075
  },
99317
- [sessions, projectRoot, notice]
100076
+ [sessions, projectRoot, noticeError]
99318
100077
  );
99319
- const applySession = (0, import_react28.useCallback)(
100078
+ const applySession = (0, import_react30.useCallback)(
99320
100079
  (loaded) => {
100080
+ retryRef.current = null;
100081
+ for (const w2 of loaded.warnings ?? []) notice(w2);
99321
100082
  sessionIdRef.current = loaded.meta.id;
99322
100083
  sessionCreatedAtRef.current = loaded.meta.createdAt;
99323
100084
  setHistory(loaded.messages);
99324
100085
  setSessionTokens(loaded.meta.tokens);
99325
100086
  setTurns([]);
99326
100087
  setEvents([]);
99327
- notice(`Resumed "${loaded.meta.title}" (${loaded.messages.length} messages, saved ${formatAge(loaded.meta.updatedAt, /* @__PURE__ */ new Date())}). Earlier output is not replayed.`);
100088
+ setReplayed(replayTurns(loaded.messages));
100089
+ setTodos(loaded.meta.todos ?? []);
100090
+ notice(`Resumed "${loaded.meta.title}" (${loaded.messages.length} messages, saved ${formatAge(loaded.meta.updatedAt, /* @__PURE__ */ new Date())}). Earlier turns are in /turns (ctrl+o).`);
99328
100091
  },
99329
- [notice]
100092
+ [notice, setTodos]
99330
100093
  );
99331
- (0, import_react28.useEffect)(() => {
100094
+ (0, import_react30.useEffect)(() => {
99332
100095
  if (!resumeLatest2) return;
99333
100096
  if (!sessions) {
99334
100097
  notice("Cannot continue: session storage is not available.");
@@ -99349,7 +100112,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99349
100112
  }
99350
100113
  })();
99351
100114
  }, [resumeLatest2, sessions, projectRoot, notice, applySession]);
99352
- const submit = (0, import_react28.useCallback)(async (prompt) => {
100115
+ const submit = (0, import_react30.useCallback)(async (prompt) => {
99353
100116
  const shellCommand = shellCommandOf(prompt);
99354
100117
  if (shellCommand !== null) {
99355
100118
  if (shellCommand === "") {
@@ -99379,8 +100142,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99379
100142
  } finally {
99380
100143
  commitTurn(prompt, shellEvents, false);
99381
100144
  setEvents([]);
99382
- pendingRef.current?.resolve(false);
99383
- setPendingConfirm(null);
100145
+ refuseAllConfirms();
99384
100146
  setIsRunning(false);
99385
100147
  }
99386
100148
  return;
@@ -99400,6 +100162,8 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99400
100162
  setSessionTokens({ input: 0, output: 0 });
99401
100163
  setTurnTokens(null);
99402
100164
  setTodos([]);
100165
+ setReplayed([]);
100166
+ retryRef.current = null;
99403
100167
  sessionIdRef.current = null;
99404
100168
  sessionCreatedAtRef.current = null;
99405
100169
  return;
@@ -99437,6 +100201,10 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99437
100201
  if (parsed.kind === "list") {
99438
100202
  const { sessions: all, errors } = await sessions.list();
99439
100203
  resumeListRef.current = all;
100204
+ if (onViewResume) {
100205
+ onViewResume({ sessions: all, errors: errors.map((e) => e.message) });
100206
+ return;
100207
+ }
99440
100208
  for (const e of errors) notice(e.message);
99441
100209
  notice(formatSessionList(all, projectRoot, /* @__PURE__ */ new Date()));
99442
100210
  return;
@@ -99517,6 +100285,10 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99517
100285
  }
99518
100286
  return;
99519
100287
  }
100288
+ case "turns":
100289
+ if (onViewTurns) onViewTurns();
100290
+ else notice("The turn viewer is not available here.");
100291
+ return;
99520
100292
  case "tools": {
99521
100293
  const tools = toolsetRef.current?.tools ?? createLocalToolset({ cwd: process.cwd(), permissions, onTodos: setTodos }).tools;
99522
100294
  notice(`Tools:
@@ -99529,6 +100301,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99529
100301
  return;
99530
100302
  }
99531
100303
  let apiKey;
100304
+ retryRef.current = null;
99532
100305
  try {
99533
100306
  apiKey = await resolveApiKey({
99534
100307
  env: process.env,
@@ -99544,14 +100317,16 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99544
100317
  return;
99545
100318
  }
99546
100319
  if (!apiKey) {
100320
+ retryRef.current = prompt;
99547
100321
  commitTurn(prompt, [{
99548
100322
  type: "error",
99549
100323
  seq: 1,
99550
100324
  message: MISSING_KEY_MESSAGE
99551
- }], false);
100325
+ }], false, void 0, true);
99552
100326
  return;
99553
100327
  }
99554
100328
  setIsRunning(true);
100329
+ setLivePrompt(prompt);
99555
100330
  setNotices([]);
99556
100331
  setEvents([]);
99557
100332
  setTurnTokens(null);
@@ -99562,6 +100337,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99562
100337
  setTurnStartedAt(startedAt);
99563
100338
  let usage = { input: 0, output: 0 };
99564
100339
  let failed = false;
100340
+ let keepForRetry = false;
99565
100341
  try {
99566
100342
  const toolset = createToolset();
99567
100343
  toolsetRef.current = toolset;
@@ -99585,13 +100361,17 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99585
100361
  const attached = await mentions.attach(prompt);
99586
100362
  if (attached.notices.length > 0) setNotices(attached.notices);
99587
100363
  const result = await runAgentLoop(attached.prompt, config, toolset.executor, emit, controller.signal, history);
99588
- setHistory(result.messages);
100364
+ const failure = [...turnEvents].reverse().find((e) => e.type === "error");
100365
+ const noProgress = result.messages.length === history.length + 1;
100366
+ keepForRetry = !controller.signal.aborted && failure !== void 0 && isRetryable(failure.message) && noProgress;
100367
+ const savedMessages = keepForRetry ? history : result.messages;
100368
+ setHistory(savedMessages);
99589
100369
  usage = { input: result.totalInputTokens, output: result.totalOutputTokens };
99590
100370
  failed = !result.success;
99591
100371
  setTurnTokens(usage);
99592
100372
  const totals = { input: sessionTokensRef.current.input + usage.input, output: sessionTokensRef.current.output + usage.output };
99593
100373
  setSessionTokens(totals);
99594
- await persist(result.messages, totals);
100374
+ if (savedMessages.length > 0) await persist(savedMessages, totals);
99595
100375
  } catch (err) {
99596
100376
  failed = true;
99597
100377
  console.error("[agent-ui] Agent loop failed:", err);
@@ -99603,21 +100383,24 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99603
100383
  } finally {
99604
100384
  const toolCount = turnEvents.filter((e) => e.type === "tool_call").length;
99605
100385
  const committedEvents = turnEvents.filter((e) => e.type !== "status");
100386
+ const retained = keepForRetry;
100387
+ retryRef.current = retained ? prompt : null;
99606
100388
  commitTurn(
99607
100389
  prompt,
99608
100390
  committedEvents,
99609
100391
  controller.signal.aborted,
99610
- formatTurnSummary({ toolCount, elapsedMs: Date.now() - startedAt, inputTokens: usage.input, outputTokens: usage.output, interrupted: controller.signal.aborted, failed })
100392
+ formatTurnSummary({ toolCount, elapsedMs: Date.now() - startedAt, inputTokens: usage.input, outputTokens: usage.output, interrupted: controller.signal.aborted, failed }, activeGlyphs().rail === "\u2502"),
100393
+ retained
99611
100394
  );
99612
100395
  setTurnStartedAt(null);
100396
+ setLivePrompt(null);
99613
100397
  setEvents([]);
99614
- pendingRef.current?.resolve(false);
99615
- setPendingConfirm(null);
100398
+ refuseAllConfirms();
99616
100399
  abortRef.current = null;
99617
100400
  setIsRunning(false);
99618
100401
  }
99619
- }, [repoName2, history, exit, permissions, notice, setPendingConfirm, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model]);
99620
- const approxTokens = (0, import_react28.useMemo)(
100402
+ }, [repoName2, history, exit, permissions, notice, refuseAllConfirms, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model, onViewTurns, onViewResume, setTodos]);
100403
+ const approxTokens = (0, import_react30.useMemo)(
99621
100404
  () => events.reduce((n, e) => n + (e.type === "text" ? estimateTokens(e.content) : e.type === "text_delta" ? estimateTokens(e.text) : 0), 0),
99622
100405
  [events]
99623
100406
  );
@@ -99628,17 +100411,47 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99628
100411
  approxTokens,
99629
100412
  ...model ? { model } : {}
99630
100413
  };
99631
- return { turns, events, isRunning, turnTokens, notices, pending, mode, todos, stats, submit, mentions };
100414
+ const retry = (0, import_react30.useCallback)(() => {
100415
+ const kept = retryRef.current;
100416
+ if (kept !== null) void submit(kept);
100417
+ }, [submit]);
100418
+ const resume = (0, import_react30.useMemo)(
100419
+ () => ({
100420
+ available: Boolean(sessions),
100421
+ load: async (meta) => {
100422
+ if (!sessions) throw new Error("Session storage is not available.");
100423
+ applySession(await sessions.load(meta.id));
100424
+ },
100425
+ remove: async (meta) => {
100426
+ if (!sessions) throw new Error("Session storage is not available.");
100427
+ await sessions.delete(meta.id);
100428
+ resumeListRef.current = resumeListRef.current.filter((m2) => m2.id !== meta.id);
100429
+ if (sessionIdRef.current === meta.id) {
100430
+ sessionIdRef.current = null;
100431
+ sessionCreatedAtRef.current = null;
100432
+ }
100433
+ },
100434
+ preview: async (meta) => {
100435
+ if (!sessions) return null;
100436
+ return sessions.preview(meta.id);
100437
+ }
100438
+ }),
100439
+ [sessions, applySession]
100440
+ );
100441
+ return { turns, events, isRunning, livePrompt, turnTokens, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume };
99632
100442
  }
99633
- var import_react28, apiUrl;
100443
+ var import_react30, apiUrl;
99634
100444
  var init_useReplController = __esm({
99635
100445
  async "src/useReplController.ts"() {
99636
100446
  "use strict";
99637
- import_react28 = __toESM(require_react(), 1);
100447
+ import_react30 = __toESM(require_react(), 1);
99638
100448
  await init_build2();
99639
100449
  init_src2();
99640
100450
  init_footer();
99641
100451
  init_session_store();
100452
+ init_replay();
100453
+ init_rail();
100454
+ init_describe_error();
99642
100455
  init_compact();
99643
100456
  init_models();
99644
100457
  init_session_commands();
@@ -99651,48 +100464,377 @@ var init_useReplController = __esm({
99651
100464
  }
99652
100465
  });
99653
100466
 
100467
+ // src/components/TurnViewer.tsx
100468
+ function TurnViewer({ turns, steps, rows, columns, onClose, onEscape }) {
100469
+ const { isRawModeSupported } = use_stdin_default();
100470
+ const glyphs = activeGlyphs();
100471
+ const [turnIdx, setTurnIdx] = (0, import_react31.useState)(null);
100472
+ const [cursorRaw, setCursor] = (0, import_react31.useState)(turns.length - 1);
100473
+ const cursor = clamp(cursorRaw, 0, Math.max(0, turns.length - 1));
100474
+ const [stepCursor, setStepCursor] = (0, import_react31.useState)(0);
100475
+ const [openId, setOpenId] = (0, import_react31.useState)(null);
100476
+ const [scroll, setScroll] = (0, import_react31.useState)(0);
100477
+ const turn = turnIdx === null ? null : turns[turnIdx] ?? null;
100478
+ const turnSteps = turn ? steps.get(turn.id) ?? [] : [];
100479
+ const detailRows = Math.max(3, rows - 6 - turnSteps.length);
100480
+ use_input_default((_input, key) => {
100481
+ if (key.escape) onEscape?.();
100482
+ if (turn === null) {
100483
+ if (key.escape) onClose();
100484
+ else if (key.upArrow) setCursor((c) => clamp(clamp(c, 0, turns.length - 1) - 1, 0, turns.length - 1));
100485
+ else if (key.downArrow) setCursor((c) => clamp(clamp(c, 0, turns.length - 1) + 1, 0, turns.length - 1));
100486
+ else if (key.return && turns.length > 0) {
100487
+ setTurnIdx(cursor);
100488
+ setStepCursor(0);
100489
+ setOpenId(null);
100490
+ setScroll(0);
100491
+ }
100492
+ return;
100493
+ }
100494
+ if (key.escape) {
100495
+ if (openId !== null) {
100496
+ setOpenId(null);
100497
+ setScroll(0);
100498
+ } else setTurnIdx(null);
100499
+ } else if (key.upArrow) {
100500
+ setStepCursor((c) => clamp(c - 1, 0, turnSteps.length - 1));
100501
+ setOpenId(null);
100502
+ setScroll(0);
100503
+ } else if (key.downArrow) {
100504
+ setStepCursor((c) => clamp(c + 1, 0, turnSteps.length - 1));
100505
+ setOpenId(null);
100506
+ setScroll(0);
100507
+ } else if (key.return && turnSteps.length > 0) {
100508
+ const id = turnSteps[stepCursor].id;
100509
+ setOpenId((cur) => cur === id ? null : id);
100510
+ setScroll(0);
100511
+ } else if (key.pageDown) setScroll((s) => s + detailRows);
100512
+ else if (key.pageUp) setScroll((s) => Math.max(0, s - detailRows));
100513
+ }, { isActive: Boolean(isRawModeSupported) });
100514
+ const listRows = Math.max(3, rows - 4);
100515
+ const listStart = clamp(cursor - listRows + 1, 0, Math.max(0, turns.length - listRows));
100516
+ if (turn === null) {
100517
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
100518
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { bold: true, children: [
100519
+ "Earlier turns ",
100520
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: "(\u2191\u2193 choose \xB7 enter open \xB7 esc close)" })
100521
+ ] }),
100522
+ turns.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: "No finished turns yet." }),
100523
+ turns.slice(listStart, listStart + listRows).map((t, k2) => {
100524
+ const idx = listStart + k2;
100525
+ const n = steps.get(t.id)?.length ?? 0;
100526
+ const prompt = t.prompt.split("\n")[0].slice(0, Math.max(10, columns - 24));
100527
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { inverse: idx === cursor, children: [
100528
+ idx === cursor ? "> " : " ",
100529
+ idx + 1,
100530
+ ". ",
100531
+ prompt,
100532
+ " ",
100533
+ glyphs.sep,
100534
+ " ",
100535
+ n,
100536
+ " step",
100537
+ n === 1 ? "" : "s"
100538
+ ] }, t.id);
100539
+ })
100540
+ ] });
100541
+ }
100542
+ const open3 = turnSteps.find((r) => r.id === openId);
100543
+ const win = open3 ? detailWindow(open3.detail, detailRows, Math.max(20, columns - 8), scroll) : null;
100544
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
100545
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: `${turnIdx + 1}. ${turn.prompt.split("\n")[0].slice(0, Math.max(10, columns - 8))}` }),
100546
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: turnSteps.length === 0 ? "No steps in this turn. (esc back)" : "\u2191\u2193 choose \xB7 enter open/close \xB7 pgup/pgdn scroll \xB7 esc back" }),
100547
+ turnSteps.map((r, i) => {
100548
+ const line = fitStepLine({ verb: r.verb, target: r.target, meta: r.meta || void 0, columns, indent: 6, ellipsis: glyphs.ellipsis, sep: glyphs.sep });
100549
+ const marker = glyphs.marker[r.state];
100550
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
100551
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { inverse: i === stepCursor, children: [
100552
+ i === stepCursor ? "> " : " ",
100553
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint(r.state === "failed" ? "delete" : "add"), children: marker }),
100554
+ " ",
100555
+ line.verb,
100556
+ " ",
100557
+ line.target,
100558
+ line.meta ? ` ${glyphs.sep} ${line.meta}` : ""
100559
+ ] }),
100560
+ r.id === openId && win && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", marginLeft: 4, children: [
100561
+ win.above > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: `${glyphs.ellipsis} ${win.above} line${win.above === 1 ? "" : "s"} above` }),
100562
+ win.lines.map((l, k2) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: l }, k2)),
100563
+ win.below > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: `${glyphs.ellipsis} ${win.below} more line${win.below === 1 ? "" : "s"}` })
100564
+ ] })
100565
+ ] }, r.id);
100566
+ })
100567
+ ] });
100568
+ }
100569
+ var import_react31, import_jsx_runtime10, clamp;
100570
+ var init_TurnViewer = __esm({
100571
+ async "src/components/TurnViewer.tsx"() {
100572
+ "use strict";
100573
+ import_react31 = __toESM(require_react(), 1);
100574
+ await init_build2();
100575
+ init_step_view();
100576
+ init_rail();
100577
+ init_theme();
100578
+ import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
100579
+ clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
100580
+ }
100581
+ });
100582
+
100583
+ // src/render/resume-rows.ts
100584
+ import path9 from "node:path";
100585
+ function orderSessions(sessions, projectRoot) {
100586
+ return [...sessions.filter((s) => s.projectRoot === projectRoot), ...sessions.filter((s) => s.projectRoot !== projectRoot)];
100587
+ }
100588
+ function formatResumeRow(meta, now, columns, sep2 = "\xB7", ellipsis = "\u2026") {
100589
+ const project = path9.basename(meta.projectRoot) || meta.projectRoot;
100590
+ const tail = [meta.turns !== void 0 ? `${meta.turns} turn${meta.turns === 1 ? "" : "s"}` : null, formatCost(estimateCostUsd(meta.tokens.input, meta.tokens.output))].filter(Boolean).join(` ${sep2} `);
100591
+ const head = `${formatAge(meta.updatedAt, now)} ${sep2} ${project} ${sep2} `;
100592
+ const fixed = head.length + 2 + ` ${sep2} ${tail}`.length;
100593
+ const room = Math.max(8, columns - fixed);
100594
+ const title = meta.title.length > room ? `${meta.title.slice(0, Math.max(1, room - ellipsis.length))}${ellipsis}` : meta.title;
100595
+ return `${head}"${title}" ${sep2} ${tail}`;
100596
+ }
100597
+ var init_resume_rows = __esm({
100598
+ "src/render/resume-rows.ts"() {
100599
+ "use strict";
100600
+ init_footer();
100601
+ init_session_store();
100602
+ }
100603
+ });
100604
+
100605
+ // src/components/ResumePicker.tsx
100606
+ function ResumePicker({ sessions, errors, projectRoot, now, rows, columns, preview, onResume, onDelete, onClose, onEscape }) {
100607
+ const { isRawModeSupported } = use_stdin_default();
100608
+ const glyphs = activeGlyphs();
100609
+ const [items, setItems] = (0, import_react32.useState)(() => orderSessions(sessions, projectRoot));
100610
+ const [cursor, setCursor] = (0, import_react32.useState)(0);
100611
+ const [stage, setStage] = (0, import_react32.useState)("browse");
100612
+ const [message, setMessage] = (0, import_react32.useState)(null);
100613
+ const [previews, setPreviews] = (0, import_react32.useState)({});
100614
+ const at = clamp2(cursor, 0, Math.max(0, items.length - 1));
100615
+ const current = items[at];
100616
+ (0, import_react32.useEffect)(() => {
100617
+ if (!current || current.id in previews) return;
100618
+ let live = true;
100619
+ preview(current).then((p) => live && setPreviews((m2) => ({ ...m2, [current.id]: p }))).catch((err) => live && setPreviews((m2) => ({ ...m2, [current.id]: err instanceof Error ? err : new Error(String(err)) })));
100620
+ return () => {
100621
+ live = false;
100622
+ };
100623
+ }, [current, preview, previews]);
100624
+ use_input_default((input, key) => {
100625
+ if (key.escape) {
100626
+ onEscape?.();
100627
+ if (stage !== "browse") {
100628
+ setStage("browse");
100629
+ setMessage(null);
100630
+ } else onClose();
100631
+ return;
100632
+ }
100633
+ if (stage === "confirm-delete") {
100634
+ if (input === "y" && current) {
100635
+ onDelete(current).then(() => {
100636
+ setItems((list2) => list2.filter((m2) => m2.id !== current.id));
100637
+ setMessage(`Deleted "${current.title}".`);
100638
+ }).catch((err) => setMessage(`Could not delete: ${err instanceof Error ? err.message : String(err)}`));
100639
+ setStage("browse");
100640
+ } else if (input === "n") {
100641
+ setStage("browse");
100642
+ setMessage(null);
100643
+ }
100644
+ return;
100645
+ }
100646
+ if (key.upArrow) {
100647
+ setCursor((c) => clamp2(c - 1, 0, items.length - 1));
100648
+ setStage("browse");
100649
+ setMessage(null);
100650
+ } else if (key.downArrow) {
100651
+ setCursor((c) => clamp2(c + 1, 0, items.length - 1));
100652
+ setStage("browse");
100653
+ setMessage(null);
100654
+ } else if (input === "d" && current) {
100655
+ setStage("confirm-delete");
100656
+ setMessage(`Delete "${current.title}"? y/n`);
100657
+ } else if (key.return && current) {
100658
+ if (current.projectRoot !== projectRoot && stage !== "confirm-foreign") {
100659
+ setStage("confirm-foreign");
100660
+ setMessage(`That session belongs to ${current.projectRoot}, not ${projectRoot}. Enter again to load it anyway, Esc to go back.`);
100661
+ return;
100662
+ }
100663
+ onResume(current).then(onClose).catch((err) => setMessage(`Could not resume: ${err instanceof Error ? err.message : String(err)}`));
100664
+ }
100665
+ }, { isActive: Boolean(isRawModeSupported) });
100666
+ const listRows = Math.max(3, rows - 8);
100667
+ const start = clamp2(at - listRows + 1, 0, Math.max(0, items.length - listRows));
100668
+ const shown = (0, import_react32.useMemo)(() => items.slice(start, start + listRows), [items, start, listRows]);
100669
+ const pv = current ? previews[current.id] : void 0;
100670
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
100671
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { bold: true, children: [
100672
+ "Resume a session ",
100673
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("secondary"), children: `(up/down choose ${glyphs.sep} enter resume ${glyphs.sep} d delete ${glyphs.sep} esc back)` })
100674
+ ] }),
100675
+ errors.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("needsYou"), children: e }, i)),
100676
+ items.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("secondary"), children: "No saved sessions yet." }),
100677
+ shown.map((m2, k2) => {
100678
+ const selected = start + k2 === at;
100679
+ const foreign = m2.projectRoot !== projectRoot;
100680
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { inverse: selected, ...foreign && !selected ? paint("secondary") : {}, children: [
100681
+ selected ? "> " : " ",
100682
+ formatResumeRow(m2, now, columns - 4, glyphs.sep, glyphs.ellipsis),
100683
+ foreign ? ` [${m2.projectRoot}]` : ""
100684
+ ] }, m2.id);
100685
+ }),
100686
+ current && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("secondary"), children: pv === void 0 ? "Last message: ..." : pv instanceof Error ? `Preview unavailable: ${pv.message}` : pv === null ? "No assistant reply saved in this session." : `Last message: "${pv}"` }),
100687
+ message && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("needsYou"), children: message })
100688
+ ] });
100689
+ }
100690
+ var import_react32, import_jsx_runtime11, clamp2;
100691
+ var init_ResumePicker = __esm({
100692
+ async "src/components/ResumePicker.tsx"() {
100693
+ "use strict";
100694
+ import_react32 = __toESM(require_react(), 1);
100695
+ await init_build2();
100696
+ init_resume_rows();
100697
+ init_rail();
100698
+ init_theme();
100699
+ import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
100700
+ clamp2 = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
100701
+ }
100702
+ });
100703
+
99654
100704
  // src/App.tsx
99655
100705
  var App_exports = {};
99656
100706
  __export(App_exports, {
99657
100707
  App: () => App2
99658
100708
  });
99659
100709
  import os4 from "node:os";
99660
- import path9 from "node:path";
99661
- function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2, yolo: yolo2, history, sessions, resumeLatest: resumeLatest2 }) {
99662
- const promptHistory = (0, import_react29.useMemo)(
99663
- () => history ?? new PromptHistory(createFileHistoryIO(path9.join(os4.homedir(), ".spacecode", "history"))),
100710
+ import path10 from "node:path";
100711
+ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2, yolo: yolo2, history, sessions, resumeLatest: resumeLatest2, git: gitRunner, interactivePicker = true }) {
100712
+ const promptHistory = (0, import_react33.useMemo)(
100713
+ () => history ?? new PromptHistory(createFileHistoryIO(path10.join(os4.homedir(), ".spacecode", "history"))),
99664
100714
  [history]
99665
100715
  );
99666
- const sessionStore = (0, import_react29.useMemo)(() => sessions ?? new SessionStore(path9.join(os4.homedir(), ".spacecode", "sessions")), [sessions]);
99667
- const { turns, events, isRunning, notices, pending, mode, todos, stats, submit, mentions } = useReplController({ repoName: repoName2, yolo: yolo2, sessions: sessionStore, resumeLatest: resumeLatest2 });
100716
+ const sessionStore = (0, import_react33.useMemo)(() => sessions ?? new SessionStore(path10.join(os4.homedir(), ".spacecode", "sessions")), [sessions]);
100717
+ const runGit = (0, import_react33.useMemo)(() => gitRunner ?? createNodeGitRunner(process.cwd()), [gitRunner]);
100718
+ const startedAtRef = (0, import_react33.useRef)(Date.now());
100719
+ const [viewingRaw, setViewing] = (0, import_react33.useState)(false);
100720
+ const escViewRef = (0, import_react33.useRef)(false);
100721
+ const escSwallowRef = (0, import_react33.useRef)(false);
100722
+ const swallowEsc = (0, import_react33.useCallback)(() => {
100723
+ escSwallowRef.current = true;
100724
+ setTimeout(() => {
100725
+ escSwallowRef.current = false;
100726
+ }, 0);
100727
+ }, []);
100728
+ const isEscOwned = (0, import_react33.useCallback)(() => escViewRef.current || escSwallowRef.current, []);
100729
+ const openViewer = (0, import_react33.useCallback)(() => setViewing(true), []);
100730
+ const { isRawModeSupported } = use_stdin_default();
100731
+ const [resumeFound, setResumeFound] = (0, import_react33.useState)(null);
100732
+ const openResume = (0, import_react33.useCallback)((found) => setResumeFound(found), []);
100733
+ const { turns, events, isRunning, livePrompt, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume } = useReplController({ repoName: repoName2, yolo: yolo2, sessions: sessionStore, resumeLatest: resumeLatest2, onViewTurns: openViewer, onViewResume: isRawModeSupported && interactivePicker ? openResume : void 0, isEscOwned });
99668
100734
  const { columns, rows } = useTerminalSize();
100735
+ const viewing = viewingRaw && !pending;
100736
+ const picking = resumeFound !== null && !pending;
100737
+ const liveSteps = (0, import_react33.useMemo)(() => stepsOfEvents(events), [events]);
100738
+ const [focusId, setFocusId] = (0, import_react33.useState)(null);
100739
+ const [openId, setOpenId] = (0, import_react33.useState)(null);
100740
+ const focusIdx = focusId === null ? -1 : liveSteps.findIndex((r) => r.id === focusId);
100741
+ const focusing = focusIdx >= 0 && !viewing && !picking && !pending;
100742
+ escViewRef.current = focusing || viewing || picking;
100743
+ const enterFocus = (0, import_react33.useCallback)(() => {
100744
+ if (liveSteps.length > 0) setFocusId(liveSteps[liveSteps.length - 1].id);
100745
+ }, [liveSteps]);
100746
+ use_input_default((input, key) => {
100747
+ if (key.escape) {
100748
+ swallowEsc();
100749
+ setFocusId(null);
100750
+ setOpenId(null);
100751
+ } else if (key.tab && !key.shift || key.downArrow) {
100752
+ setFocusId(liveSteps[(focusIdx + 1) % liveSteps.length].id);
100753
+ setOpenId(null);
100754
+ } else if (key.upArrow) {
100755
+ setFocusId(liveSteps[(focusIdx + liveSteps.length - 1) % liveSteps.length].id);
100756
+ setOpenId(null);
100757
+ } else if (key.return || input === " ") {
100758
+ setOpenId((cur) => cur === focusId ? null : focusId);
100759
+ }
100760
+ }, { isActive: Boolean(isRawModeSupported) && focusing });
100761
+ use_input_default((input, key) => {
100762
+ if (key.ctrl && input === "o") setViewing(true);
100763
+ }, { isActive: Boolean(isRawModeSupported) && !viewing && !picking && !pending });
100764
+ const waitingStep = pending ? findBlockedStep(liveSteps, pending.req) : void 0;
100765
+ const promptNode = pending ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PermissionPrompt, { req: pending.req, onDecide: pending.resolve }, pending.id) : null;
100766
+ const viewerTurns = (0, import_react33.useMemo)(() => [...replayed, ...turns], [replayed, turns]);
100767
+ const turnSteps = (0, import_react33.useMemo)(() => viewing ? stepsOfTurns(viewerTurns) : /* @__PURE__ */ new Map(), [viewing, viewerTurns]);
100768
+ const [gitState, setGitState] = (0, import_react33.useState)(null);
100769
+ (0, import_react33.useEffect)(() => {
100770
+ let live = true;
100771
+ void readGitState(runGit).then((g2) => {
100772
+ if (live) setGitState(g2);
100773
+ });
100774
+ return () => {
100775
+ live = false;
100776
+ };
100777
+ }, [runGit, turns.length]);
99669
100778
  const footer = formatFooter({ mode, contextPct: stats.contextPct, costUsd: stats.costUsd, model: stats.model, columns });
99670
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", children: [
99671
- notices.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "cyan", children: n }, i)),
99672
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Static, { items: [BANNER, ...turns], children: (turn) => typeof turn === "symbol" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(WelcomeBanner, { repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2 }, "banner") : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", children: [
99673
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: "gray", children: [
100779
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", children: [
100780
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Header, { project: repoName2 ?? path10.basename(process.cwd()), git: gitState, turns: turns.length, elapsedMs: Date.now() - startedAtRef.current, columns }),
100781
+ notices.map((n, i) => typeof n === "string" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("action"), children: n }, i) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorBlock, { view: n.error }, i)),
100782
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Static, { items: [BANNER, ...turns], children: (turn) => typeof turn === "symbol" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(WelcomeBanner, { repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2 }, "banner") : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", children: [
100783
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { ...paint("secondary"), children: [
99674
100784
  "> ",
99675
100785
  turn.prompt
99676
100786
  ] }),
99677
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EventRenderer, { events: turn.events }),
99678
- turn.interrupted && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "yellow", children: "Interrupted" }),
99679
- turn.summary && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { dimColor: true, children: turn.summary })
100787
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EventRenderer, { events: turn.events, promptSaved: turn.retained }),
100788
+ turn.interrupted && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("needsYou"), children: "Interrupted" }),
100789
+ turn.summary && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("secondary"), children: turn.summary })
99680
100790
  ] }, turn.id) }),
99681
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EventRenderer, { events, liveMaxLines: Math.max(6, rows - 12) }),
99682
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TodoChecklist, { todos }),
99683
- pending && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PermissionPrompt, { req: pending.req, onDecide: pending.resolve }),
99684
- isRunning && !pending && stats.elapsedMs !== null && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { dimColor: true, children: formatThinking(stats.elapsedMs, stats.approxTokens) }),
99685
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PromptEditor, { onSubmit: submit, disabled: isRunning, history: promptHistory, listFiles: mentions.list }),
99686
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: footer.level === "critical" ? "red" : footer.level === "warn" ? "yellow" : void 0, dimColor: footer.level === "ok", children: footer.text })
100791
+ viewing && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TurnViewer, { turns: viewerTurns, steps: turnSteps, rows, columns, onClose: () => setViewing(false), onEscape: swallowEsc }),
100792
+ picking && resumeFound && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
100793
+ ResumePicker,
100794
+ {
100795
+ sessions: resumeFound.sessions,
100796
+ errors: resumeFound.errors,
100797
+ projectRoot: process.cwd(),
100798
+ now: /* @__PURE__ */ new Date(),
100799
+ rows,
100800
+ columns,
100801
+ preview: resume.preview,
100802
+ onResume: resume.load,
100803
+ onDelete: resume.remove,
100804
+ onClose: () => setResumeFound(null),
100805
+ onEscape: swallowEsc
100806
+ }
100807
+ ),
100808
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", display: viewing || picking ? "none" : "flex", children: [
100809
+ livePrompt !== null && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { ...paint("secondary"), children: [
100810
+ "> ",
100811
+ livePrompt
100812
+ ] }),
100813
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EventRenderer, { deferErrors: true, events, liveMaxLines: Math.max(6, rows - 15), focusId: focusing ? focusId : null, openId: focusing ? openId : null, detailRows: Math.max(4, rows - 14), waitingId: waitingStep?.id ?? null, prompt: promptNode }),
100814
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TodoChecklist, { todos }),
100815
+ pending && !waitingStep && promptNode,
100816
+ isRunning && !pending && stats.elapsedMs !== null && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("secondary"), children: formatThinking(stats.elapsedMs, stats.approxTokens, activeGlyphs().rail === "\u2502") }),
100817
+ focusing && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("secondary"), children: activeGlyphs().rail === "\u2502" ? "step focus: \u2191\u2193 or tab move \xB7 enter open/close \xB7 esc back to the prompt" : "step focus: up/down or tab move - enter open/close - esc back to the prompt" }),
100818
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PromptEditor, { onEmptyEnter: isRunning ? void 0 : retry, promptGlyph: promptGlyph(mode, activeGlyphs().rail === "\u2502"), unicode: activeGlyphs().rail === "\u2502", onSubmit: submit, disabled: isRunning, history: promptHistory, listFiles: mentions.list, onTab: enterFocus, locked: focusing || viewing || picking || Boolean(pending) }),
100819
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...footer.level === "critical" ? paint("delete") : footer.level === "warn" ? paint("needsYou") : paint("secondary"), children: footer.text }),
100820
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("secondary"), children: formatHints({ running: isRunning, asking: Boolean(pending), columns }, activeGlyphs().sep) })
100821
+ ] })
99687
100822
  ] });
99688
100823
  }
99689
- var import_react29, import_jsx_runtime8, BANNER;
100824
+ var import_react33, import_jsx_runtime12, BANNER;
99690
100825
  var init_App2 = __esm({
99691
100826
  async "src/App.tsx"() {
99692
100827
  "use strict";
99693
- import_react29 = __toESM(require_react(), 1);
100828
+ import_react33 = __toESM(require_react(), 1);
99694
100829
  await init_build2();
99695
100830
  await init_WelcomeBanner();
100831
+ await init_Header();
100832
+ await init_ErrorBlock();
100833
+ init_git_status();
100834
+ init_node_git();
100835
+ init_hints();
100836
+ init_mode_line();
100837
+ init_rail();
99696
100838
  await init_EventRenderer();
99697
100839
  await init_PermissionPrompt();
99698
100840
  await init_TodoChecklist();
@@ -99703,7 +100845,11 @@ var init_App2 = __esm({
99703
100845
  await init_useReplController();
99704
100846
  await init_useTerminalSize();
99705
100847
  init_session_store();
99706
- import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
100848
+ init_theme();
100849
+ await init_TurnViewer();
100850
+ await init_ResumePicker();
100851
+ init_step_view();
100852
+ import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
99707
100853
  BANNER = /* @__PURE__ */ Symbol("welcome-banner");
99708
100854
  }
99709
100855
  });
@@ -99724,7 +100870,7 @@ function hideCiVariables(env3, stdoutIsTTY) {
99724
100870
  }
99725
100871
 
99726
100872
  // src/index.tsx
99727
- var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
100873
+ var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
99728
100874
  var repoName = process.env.REPO_NAME || process.cwd().split("/").pop() || void 0;
99729
100875
  var validModes = ["guest", "new_project", "existing_repo"];
99730
100876
  var rawMode = process.env.ONBOARDING_MODE;
@@ -99741,7 +100887,7 @@ try {
99741
100887
  } finally {
99742
100888
  restoreCiVariables();
99743
100889
  }
99744
- render2(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(App3, { repoName, onboardingMode, launchCount, yolo, resumeLatest }));
100890
+ render2(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(App3, { repoName, onboardingMode, launchCount, yolo, resumeLatest, interactivePicker: Boolean(process.stdout.isTTY) }));
99745
100891
  /*! Bundled license information:
99746
100892
 
99747
100893
  react/cjs/react.production.min.js: