spacecode 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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})`;
95806
- }
95807
- let value = "";
95808
- for (const [, v2] of Object.entries(input)) {
95809
- if (typeof v2 === "string") {
95810
- value = v2;
95811
- break;
95812
- }
96036
+ return { verb: "Todos", target: `${n} item${n === 1 ? "" : "s"}` };
95813
96037
  }
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
96012
96766
  ] });
96013
96767
  }
96014
- var import_jsx_runtime3, MAX_ERROR_LINES, MAX_RUNNING_LINE_CHARS, MAX_SUMMARY_CHARS;
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"}` })
96775
+ ] });
96776
+ }
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,41 @@ 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 }) {
96559
97396
  const { exit } = use_app_default();
96560
97397
  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);
97398
+ const [buf, setBuf] = (0, import_react29.useState)(() => createBuffer());
97399
+ const [hint, setHint] = (0, import_react29.useState)(null);
97400
+ const bufRef = (0, import_react29.useRef)(buf);
97401
+ const queue = (0, import_react29.useRef)([]);
97402
+ const [files, setFiles] = (0, import_react29.useState)(null);
97403
+ const filesRef = (0, import_react29.useRef)(null);
97404
+ const loadingFiles = (0, import_react29.useRef)(false);
97405
+ const [sel, setSel] = (0, import_react29.useState)(0);
97406
+ const selRef = (0, import_react29.useRef)(0);
97407
+ const [shell, setShellState] = (0, import_react29.useState)(false);
97408
+ const shellRef = (0, import_react29.useRef)(false);
96572
97409
  const setShell = (on) => {
96573
97410
  shellRef.current = on;
96574
97411
  setShellState(on);
96575
97412
  };
96576
97413
  bufRef.current = buf;
96577
- const fail = (0, import_react27.useCallback)((what, err) => {
97414
+ const fail = (0, import_react29.useCallback)((what, err) => {
96578
97415
  setHint(`${what}: ${err instanceof Error ? err.message : String(err)}`);
96579
97416
  }, []);
96580
- (0, import_react27.useEffect)(() => {
97417
+ (0, import_react29.useEffect)(() => {
96581
97418
  history?.load().catch((err) => fail("Could not load prompt history", err));
96582
97419
  }, [history, fail]);
96583
- const dispatch = (0, import_react27.useCallback)((text) => {
97420
+ const dispatch = (0, import_react29.useCallback)((text) => {
96584
97421
  const result = onSubmit(text);
96585
97422
  if (result instanceof Promise) result.catch((err) => fail("Submit failed", err));
96586
97423
  }, [onSubmit, fail]);
96587
- (0, import_react27.useEffect)(() => {
97424
+ (0, import_react29.useEffect)(() => {
96588
97425
  if (!disabled && queue.current.length > 0) dispatch(queue.current.shift());
96589
97426
  }, [disabled, dispatch]);
96590
97427
  const mention = shell ? null : activeMention(buf.text, buf.cursor);
96591
97428
  const mentionOpen = mention !== null;
96592
- (0, import_react27.useEffect)(() => {
97429
+ (0, import_react29.useEffect)(() => {
96593
97430
  if (!mentionOpen || !listFiles2 || filesRef.current !== null || loadingFiles.current) return;
96594
97431
  loadingFiles.current = true;
96595
97432
  listFiles2().then((f) => {
@@ -96659,6 +97496,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
96659
97496
  case "tab": {
96660
97497
  const completed = completeSlash(cur.text, slashMatches(cur.text, SLASH_COMMANDS));
96661
97498
  if (completed) update(createBuffer(completed));
97499
+ else onTab?.();
96662
97500
  return;
96663
97501
  }
96664
97502
  case "historyPrev":
@@ -96685,6 +97523,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
96685
97523
  const typed = cur.text.trim();
96686
97524
  if (typed === "") {
96687
97525
  if (inShell) setHint("Type a command after ! (Backspace on an empty prompt leaves shell mode)");
97526
+ else onEmptyEnter?.();
96688
97527
  return;
96689
97528
  }
96690
97529
  const text = inShell ? `!${typed}` : typed;
@@ -96702,9 +97541,9 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
96702
97541
  else dispatch(text);
96703
97542
  }
96704
97543
  }
96705
- }, { isActive: Boolean(isRawModeSupported) });
97544
+ }, { isActive: Boolean(isRawModeSupported) && !locked });
96706
97545
  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.' });
97546
+ 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
97547
  }
96709
97548
  if (disabled && buf.text === "") return null;
96710
97549
  const rows = lines(buf);
@@ -96712,26 +97551,26 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2 }) {
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 })
97554
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", children: [
97555
+ 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
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { dimColor: disabled, children: i === line && !disabled ? withCursor(row, col) : row })
96719
97558
  ] }, i)),
96720
- matches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { dimColor: true, children: [
97559
+ matches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { ...paint("secondary"), children: [
96721
97560
  matches.map((c) => `/${c.name} \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 ? "\u203A" : " "} ${p}` }, p)),
97565
+ picks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...paint("secondary"), children: "(\u2191/\u2193 select, Tab or Enter to insert)" }),
97566
+ hint && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { ...paint("delete"), children: hint })
96728
97567
  ] });
96729
97568
  }
96730
- var import_react27, import_jsx_runtime7, PICKER_ROWS;
97569
+ var import_react29, import_jsx_runtime9, PICKER_ROWS;
96731
97570
  var init_PromptEditor = __esm({
96732
97571
  async "src/components/PromptEditor.tsx"() {
96733
97572
  "use strict";
96734
- import_react27 = __toESM(require_react(), 1);
97573
+ import_react29 = __toESM(require_react(), 1);
96735
97574
  await init_build2();
96736
97575
  init_edit_buffer();
96737
97576
  init_editor_keys();
@@ -96739,33 +97578,12 @@ var init_PromptEditor = __esm({
96739
97578
  init_mention_token();
96740
97579
  init_slash_menu();
96741
97580
  init_slash_commands();
96742
- import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
97581
+ init_theme();
97582
+ import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
96743
97583
  PICKER_ROWS = 6;
96744
97584
  }
96745
97585
  });
96746
97586
 
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
97587
  // src/editor/history-store.ts
96770
97588
  var PromptHistory;
96771
97589
  var init_history_store = __esm({
@@ -96847,24 +97665,24 @@ var init_history_store = __esm({
96847
97665
  });
96848
97666
 
96849
97667
  // src/editor/history-file-io.ts
96850
- import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
96851
- import path from "node:path";
97668
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
97669
+ import path2 from "node:path";
96852
97670
  function createFileHistoryIO(file) {
96853
97671
  return {
96854
97672
  async read() {
96855
97673
  try {
96856
- return await readFile(file, "utf8");
97674
+ return await readFile2(file, "utf8");
96857
97675
  } catch (err) {
96858
97676
  if (err.code === "ENOENT") return null;
96859
97677
  throw err;
96860
97678
  }
96861
97679
  },
96862
97680
  async write(content) {
96863
- await mkdir(path.dirname(file), { recursive: true, mode: 448 });
97681
+ await mkdir2(path2.dirname(file), { recursive: true, mode: 448 });
96864
97682
  const tmp = `${file}.${process.pid}.tmp`;
96865
- await writeFile(tmp, content, { mode: 384 });
96866
- await chmod(tmp, 384);
96867
- await rename(tmp, file);
97683
+ await writeFile2(tmp, content, { mode: 384 });
97684
+ await chmod2(tmp, 384);
97685
+ await rename2(tmp, file);
96868
97686
  }
96869
97687
  };
96870
97688
  }
@@ -97310,6 +98128,7 @@ async function runAgentLoop(prompt, config, executor, emit, signal, existingMess
97310
98128
  }
97311
98129
  lastToolCount = toolUseBlocks.length;
97312
98130
  if (data.stop_reason !== "tool_use") {
98131
+ if (data.content.length > 0) messages.push({ role: "assistant", content: data.content });
97313
98132
  emitDone(emit, {
97314
98133
  type: "done",
97315
98134
  success: true,
@@ -97539,9 +98358,9 @@ function createFileTools(store) {
97539
98358
  readCache.clear();
97540
98359
  listCache.clear();
97541
98360
  }
97542
- function invalidatePath(path10) {
97543
- readCache.delete(path10);
97544
- const parent = path10.includes("/") ? path10.slice(0, path10.lastIndexOf("/") + 1) : "";
98361
+ function invalidatePath(path11) {
98362
+ readCache.delete(path11);
98363
+ const parent = path11.includes("/") ? path11.slice(0, path11.lastIndexOf("/") + 1) : "";
97545
98364
  listCache.delete(parent);
97546
98365
  listCache.delete("");
97547
98366
  listCache.delete(".");
@@ -97549,36 +98368,36 @@ function createFileTools(store) {
97549
98368
  }
97550
98369
  const handlers = {
97551
98370
  read_file: async (input) => {
97552
- const path10 = input.path;
97553
- const cached = readCache.get(path10);
98371
+ const path11 = input.path;
98372
+ const cached = readCache.get(path11);
97554
98373
  if (cached !== void 0) {
97555
98374
  return { output: cached };
97556
98375
  }
97557
- const content = await store.readFile(path10);
98376
+ const content = await store.readFile(path11);
97558
98377
  if (content === null) {
97559
- return { output: `File not found: ${path10}`, is_error: true };
98378
+ return { output: `File not found: ${path11}`, is_error: true };
97560
98379
  }
97561
- readCache.set(path10, content);
98380
+ readCache.set(path11, content);
97562
98381
  return { output: content };
97563
98382
  },
97564
98383
  write_file: async (input) => {
97565
- const path10 = input.path;
98384
+ const path11 = input.path;
97566
98385
  const content = input.content;
97567
- await store.writeFile(path10, content);
97568
- invalidatePath(path10);
97569
- readCache.set(path10, content);
97570
- return { output: `Written: ${path10}` };
98386
+ await store.writeFile(path11, content);
98387
+ invalidatePath(path11);
98388
+ readCache.set(path11, content);
98389
+ return { output: `Written: ${path11}` };
97571
98390
  },
97572
98391
  edit_file: async (input) => {
97573
- const path10 = input.path;
98392
+ const path11 = input.path;
97574
98393
  const oldStr = input.old_string;
97575
98394
  const newStr = input.new_string;
97576
- let content = readCache.get(path10);
98395
+ let content = readCache.get(path11);
97577
98396
  if (content === void 0) {
97578
- const stored = await store.readFile(path10);
98397
+ const stored = await store.readFile(path11);
97579
98398
  if (stored === null) {
97580
98399
  return {
97581
- output: `File not found: ${path10}. Did you read the file first?`,
98400
+ output: `File not found: ${path11}. Did you read the file first?`,
97582
98401
  is_error: true
97583
98402
  };
97584
98403
  }
@@ -97588,41 +98407,41 @@ function createFileTools(store) {
97588
98407
  if (lineNumbers.length === 0) {
97589
98408
  const lineCount = content.split("\n").length;
97590
98409
  return {
97591
- output: `old_string not found in ${path10}. The file has ${lineCount} lines. Did you read the file first?`,
98410
+ output: `old_string not found in ${path11}. The file has ${lineCount} lines. Did you read the file first?`,
97592
98411
  is_error: true
97593
98412
  };
97594
98413
  }
97595
98414
  if (lineNumbers.length > 1) {
97596
98415
  const locations = lineNumbers.map((ln) => ` - line ${ln}`).join("\n");
97597
98416
  return {
97598
- output: `old_string matches ${lineNumbers.length} locations in ${path10}:
98417
+ output: `old_string matches ${lineNumbers.length} locations in ${path11}:
97599
98418
  ${locations}
97600
98419
  Include more surrounding context to make old_string unique.`,
97601
98420
  is_error: true
97602
98421
  };
97603
98422
  }
97604
- const result = await store.editFile(path10, oldStr, newStr);
98423
+ const result = await store.editFile(path11, oldStr, newStr);
97605
98424
  if (!result.success) {
97606
98425
  return { output: result.error, is_error: true };
97607
98426
  }
97608
- invalidatePath(path10);
97609
- const updated = await store.readFile(path10);
97610
- if (updated !== null) readCache.set(path10, updated);
97611
- return { output: `Edited: ${path10}` };
98427
+ invalidatePath(path11);
98428
+ const updated = await store.readFile(path11);
98429
+ if (updated !== null) readCache.set(path11, updated);
98430
+ return { output: `Edited: ${path11}` };
97612
98431
  },
97613
98432
  list_directory: async (input) => {
97614
- const path10 = input.path;
97615
- const cached = listCache.get(path10);
98433
+ const path11 = input.path;
98434
+ const cached = listCache.get(path11);
97616
98435
  if (cached !== void 0) {
97617
98436
  if (cached.length === 0) {
97618
- return { output: `Empty or not found: ${path10}` };
98437
+ return { output: `Empty or not found: ${path11}` };
97619
98438
  }
97620
98439
  return { output: cached.join("\n") };
97621
98440
  }
97622
- const entries = await store.listDirectory(path10);
97623
- listCache.set(path10, entries);
98441
+ const entries = await store.listDirectory(path11);
98442
+ listCache.set(path11, entries);
97624
98443
  if (entries.length === 0) {
97625
- return { output: `Empty or not found: ${path10}` };
98444
+ return { output: `Empty or not found: ${path11}` };
97626
98445
  }
97627
98446
  return { output: entries.join("\n") };
97628
98447
  }
@@ -97858,8 +98677,8 @@ function parseTodos(input) {
97858
98677
  if (typeof e.status !== "string" || !STATUSES.includes(e.status)) throw new Error(`Item ${i + 1}: "status" must be one of ${STATUSES.join(", ")}`);
97859
98678
  return { content: e.content.trim(), status: e.status };
97860
98679
  });
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})`);
98680
+ const active2 = items.filter((t) => t.status === "in_progress").length;
98681
+ if (active2 > 1) throw new Error(`Only one item may be "in_progress" at a time (found ${active2})`);
97863
98682
  return items;
97864
98683
  }
97865
98684
  function createTodoTool(onUpdate) {
@@ -97958,9 +98777,9 @@ var init_host = __esm({
97958
98777
  });
97959
98778
 
97960
98779
  // ../agent/src/host/local-host.ts
97961
- import { execFile } from "node:child_process";
98780
+ import { execFile as execFile2 } from "node:child_process";
97962
98781
  import { promises as fs2 } from "node:fs";
97963
- import * as path2 from "node:path";
98782
+ import * as path3 from "node:path";
97964
98783
  var LocalHost;
97965
98784
  var init_local_host = __esm({
97966
98785
  "../agent/src/host/local-host.ts"() {
@@ -97970,7 +98789,7 @@ var init_local_host = __esm({
97970
98789
  files;
97971
98790
  root;
97972
98791
  constructor(rootDir) {
97973
- this.root = path2.resolve(rootDir);
98792
+ this.root = path3.resolve(rootDir);
97974
98793
  this.files = {
97975
98794
  readFile: (p) => this.readFile(p),
97976
98795
  writeFile: (p, c) => this.writeFile(p, c),
@@ -97979,12 +98798,12 @@ var init_local_host = __esm({
97979
98798
  };
97980
98799
  }
97981
98800
  static inside(root, abs) {
97982
- const rel = path2.relative(root, abs);
97983
- return !(rel === ".." || rel.startsWith(".." + path2.sep) || path2.isAbsolute(rel));
98801
+ const rel = path3.relative(root, abs);
98802
+ return !(rel === ".." || rel.startsWith(".." + path3.sep) || path3.isAbsolute(rel));
97984
98803
  }
97985
98804
  /** Lexical confinement plus a symlink-aware check of the deepest existing ancestor. */
97986
98805
  async resolve(p) {
97987
- const abs = path2.resolve(this.root, p);
98806
+ const abs = path3.resolve(this.root, p);
97988
98807
  if (!_LocalHost.inside(this.root, abs)) throw new Error(`Path escapes workspace root: ${p}`);
97989
98808
  const realRoot = await fs2.realpath(this.root);
97990
98809
  let probe = abs;
@@ -98002,7 +98821,7 @@ var init_local_host = __esm({
98002
98821
  throw e;
98003
98822
  });
98004
98823
  if (st?.isSymbolicLink()) throw new Error(`Path is a dangling symlink: ${p}`);
98005
- const parent = path2.dirname(probe);
98824
+ const parent = path3.dirname(probe);
98006
98825
  if (parent === probe) break;
98007
98826
  probe = parent;
98008
98827
  }
@@ -98019,7 +98838,7 @@ var init_local_host = __esm({
98019
98838
  }
98020
98839
  async writeFile(p, content) {
98021
98840
  const abs = await this.resolve(p);
98022
- await fs2.mkdir(path2.dirname(abs), { recursive: true });
98841
+ await fs2.mkdir(path3.dirname(abs), { recursive: true });
98023
98842
  await fs2.writeFile(abs, content, "utf8");
98024
98843
  }
98025
98844
  async editFile(p, oldStr, newStr) {
@@ -98049,7 +98868,7 @@ var init_local_host = __esm({
98049
98868
  */
98050
98869
  exec(command, onOutput) {
98051
98870
  return new Promise((resolve2, reject) => {
98052
- const child = execFile(
98871
+ const child = execFile2(
98053
98872
  "/bin/sh",
98054
98873
  ["-c", command],
98055
98874
  { cwd: this.root, maxBuffer: 10 * 1024 * 1024 },
@@ -98311,7 +99130,7 @@ var init_policy = __esm({
98311
99130
 
98312
99131
  // ../agent/src/host/audit-log.ts
98313
99132
  import { promises as fs3 } from "node:fs";
98314
- import * as path3 from "node:path";
99133
+ import * as path4 from "node:path";
98315
99134
  function truncate(e) {
98316
99135
  return e.subject.length > MAX_SUBJECT ? { ...e, subject: e.subject.slice(0, MAX_SUBJECT) + "...[truncated]" } : e;
98317
99136
  }
@@ -98325,7 +99144,7 @@ var init_audit_log = __esm({
98325
99144
  this.file = file;
98326
99145
  }
98327
99146
  async record(entry) {
98328
- await fs3.mkdir(path3.dirname(this.file), { recursive: true });
99147
+ await fs3.mkdir(path4.dirname(this.file), { recursive: true });
98329
99148
  await fs3.appendFile(this.file, JSON.stringify(truncate(entry)) + "\n", { mode: 384 });
98330
99149
  }
98331
99150
  };
@@ -98570,136 +99389,39 @@ var init_src2 = __esm({
98570
99389
  }
98571
99390
  });
98572
99391
 
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
- }
99392
+ // src/session/replay.ts
99393
+ function replayTurns(messages) {
99394
+ const turns = [];
99395
+ let current = null;
99396
+ let seq = 0;
99397
+ const push = (event) => current?.events.push({ ...event, seq: ++seq });
99398
+ for (const message of messages) {
99399
+ const blocks = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
99400
+ if (message.role === "user") {
99401
+ const text = blocks.filter((b2) => b2.type === "text").map((b2) => b2.text ?? "").join("\n");
99402
+ if (text !== "" || !current) {
99403
+ current = { id: -(turns.length + 1), prompt: text || "(no prompt text)", events: [], interrupted: false };
99404
+ turns.push(current);
98662
99405
  }
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 });
99406
+ for (const b2 of blocks) {
99407
+ if (b2.type === "tool_result" && b2.tool_use_id) {
99408
+ push({ type: "tool_result", id: b2.tool_use_id, output: contentText(b2.content), is_error: b2.is_error ?? false });
98670
99409
  }
98671
- return parseSession(file, text);
98672
99410
  }
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 });
98681
- }
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 };
99411
+ } else if (current) {
99412
+ for (const b2 of blocks) {
99413
+ if (b2.type === "text" && b2.text) push({ type: "text", content: b2.text });
99414
+ else if (b2.type === "tool_use" && b2.id && b2.name) push({ type: "tool_call", id: b2.id, tool: b2.name, input: b2.input ?? {} });
98693
99415
  }
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
- }
98701
- }
98702
- };
99416
+ }
99417
+ }
99418
+ return turns;
99419
+ }
99420
+ var contentText;
99421
+ var init_replay = __esm({
99422
+ "src/session/replay.ts"() {
99423
+ "use strict";
99424
+ contentText = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.map((b2) => b2 && typeof b2 === "object" && "text" in b2 ? String(b2.text ?? "") : "").join("") : "";
98703
99425
  }
98704
99426
  });
98705
99427
 
@@ -99090,7 +99812,7 @@ var init_mention_resolver = __esm({
99090
99812
  });
99091
99813
 
99092
99814
  // src/editor/node-mention-fs.ts
99093
- import { open, realpath, stat } from "node:fs/promises";
99815
+ import { open as open2, realpath, stat as stat2 } from "node:fs/promises";
99094
99816
  import path6 from "node:path";
99095
99817
  function isInside(root, target) {
99096
99818
  return target === root || target.startsWith(root.endsWith(path6.sep) ? root : root + path6.sep);
@@ -99113,13 +99835,13 @@ function createNodeMentionFS(cwd2) {
99113
99835
  const real = await confined(abs);
99114
99836
  if (real === null) return null;
99115
99837
  if (real === "") return { size: 0, isFile: false };
99116
- const st = await stat(real);
99838
+ const st = await stat2(real);
99117
99839
  return { size: st.size, isFile: st.isFile() };
99118
99840
  },
99119
99841
  async readFile(abs, maxBytes) {
99120
99842
  const real = await confined(abs);
99121
99843
  if (!real) throw new Error(`refusing to read outside cwd: ${abs}`);
99122
- const fh = await open(real, "r");
99844
+ const fh = await open2(real, "r");
99123
99845
  try {
99124
99846
  const buf = Buffer.alloc(maxBytes);
99125
99847
  const { bytesRead } = await fh.read(buf, 0, maxBytes, 0);
@@ -99226,109 +99948,143 @@ var init_api_key = __esm({
99226
99948
  // src/useReplController.ts
99227
99949
  import { readFile as readFile4 } from "node:fs/promises";
99228
99950
  import { homedir as homedir2 } from "node:os";
99229
- function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2 }) {
99951
+ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
99230
99952
  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)(() => {
99953
+ const mentions = (0, import_react30.useMemo)(() => mentionsOpt ?? createNodeMentions(process.cwd()), [mentionsOpt]);
99954
+ const [events, setEvents] = (0, import_react30.useState)([]);
99955
+ const [turns, setTurns] = (0, import_react30.useState)([]);
99956
+ const turnIdRef = (0, import_react30.useRef)(0);
99957
+ const [isRunning, setIsRunning] = (0, import_react30.useState)(false);
99958
+ const [history, setHistory] = (0, import_react30.useState)([]);
99959
+ const [turnTokens, setTurnTokens] = (0, import_react30.useState)(null);
99960
+ const [sessionTokens, setSessionTokens] = (0, import_react30.useState)({ input: 0, output: 0 });
99961
+ const [turnStartedAt, setTurnStartedAt] = (0, import_react30.useState)(null);
99962
+ const [now, setNow] = (0, import_react30.useState)(() => Date.now());
99963
+ (0, import_react30.useEffect)(() => {
99242
99964
  if (turnStartedAt === null) return;
99243
99965
  setNow(Date.now());
99244
99966
  const timer = setInterval(() => setNow(Date.now()), 1e3);
99245
99967
  return () => clearInterval(timer);
99246
99968
  }, [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)(() => {
99969
+ const projectRoot = (0, import_react30.useMemo)(() => process.cwd(), []);
99970
+ const sessionIdRef = (0, import_react30.useRef)(null);
99971
+ const sessionCreatedAtRef = (0, import_react30.useRef)(null);
99972
+ const sessionTokensRef = (0, import_react30.useRef)({ input: 0, output: 0 });
99973
+ const resumeListRef = (0, import_react30.useRef)([]);
99974
+ const [model, setModel] = (0, import_react30.useState)(null);
99975
+ const [todos, setTodosState] = (0, import_react30.useState)([]);
99976
+ const todosRef = (0, import_react30.useRef)([]);
99977
+ const setTodos = (0, import_react30.useCallback)((t) => {
99978
+ todosRef.current = t;
99979
+ setTodosState(t);
99980
+ }, []);
99981
+ const [replayed, setReplayed] = (0, import_react30.useState)([]);
99982
+ const [notices, setNotices] = (0, import_react30.useState)([]);
99983
+ const retryRef = (0, import_react30.useRef)(null);
99984
+ const [pending, setPending] = (0, import_react30.useState)(null);
99985
+ const abortRef = (0, import_react30.useRef)(null);
99986
+ const pendingRef = (0, import_react30.useRef)(null);
99987
+ const toolsetRef = (0, import_react30.useRef)(null);
99988
+ (0, import_react30.useEffect)(() => {
99260
99989
  sessionTokensRef.current = sessionTokens;
99261
99990
  }, [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);
99991
+ const [permissions] = (0, import_react30.useState)(() => new PermissionState(yolo2 ? "yolo" : "default"));
99992
+ const [mode, setMode] = (0, import_react30.useState)(permissions.mode);
99993
+ (0, import_react30.useEffect)(() => permissions.onChange(setMode), [permissions]);
99994
+ const answeredThisKeypressRef = (0, import_react30.useRef)(false);
99995
+ const confirmQueueRef = (0, import_react30.useRef)([]);
99996
+ const confirmIdRef = (0, import_react30.useRef)(0);
99997
+ const showNextConfirm = (0, import_react30.useCallback)(() => {
99998
+ const head = confirmQueueRef.current[0] ?? null;
99999
+ pendingRef.current = head;
100000
+ setPending(head);
99269
100001
  }, []);
99270
- const commitTurn = (0, import_react28.useCallback)((prompt, turnEvents, interrupted, summary) => {
99271
- setTurns((prev) => [...prev, { id: turnIdRef.current++, prompt, events: turnEvents, interrupted, summary }]);
100002
+ const enqueueConfirm = (0, import_react30.useCallback)((p) => {
100003
+ confirmQueueRef.current.push(p);
100004
+ if (confirmQueueRef.current.length === 1) showNextConfirm();
100005
+ }, [showNextConfirm]);
100006
+ const dequeueConfirm = (0, import_react30.useCallback)((p) => {
100007
+ confirmQueueRef.current = confirmQueueRef.current.filter((q2) => q2 !== p);
100008
+ showNextConfirm();
100009
+ }, [showNextConfirm]);
100010
+ const refuseAllConfirms = (0, import_react30.useCallback)(() => {
100011
+ const all = confirmQueueRef.current;
100012
+ confirmQueueRef.current = [];
100013
+ for (const p of all) p.resolve(false);
100014
+ pendingRef.current = null;
100015
+ setPending(null);
99272
100016
  }, []);
99273
- const notice = (0, import_react28.useCallback)((message) => {
100017
+ const commitTurn = (0, import_react30.useCallback)((prompt, turnEvents, interrupted, summary, retained) => {
100018
+ setTurns((prev) => [...prev, { id: turnIdRef.current++, prompt, events: turnEvents, interrupted, summary, retained }]);
100019
+ }, []);
100020
+ const notice = (0, import_react30.useCallback)((message) => {
99274
100021
  setNotices((prev) => [...prev, message]);
99275
100022
  }, []);
100023
+ const noticeError = (0, import_react30.useCallback)((view) => {
100024
+ setNotices((prev) => [...prev, { error: view }]);
100025
+ }, []);
99276
100026
  const { isRawModeSupported } = use_stdin_default();
99277
100027
  use_input_default((_input, key) => {
99278
100028
  if (key.shift && key.tab) {
99279
100029
  permissions.cycle();
99280
100030
  return;
99281
100031
  }
99282
- if (pendingRef.current || answeredThisKeypressRef.current) return;
100032
+ if (pendingRef.current || answeredThisKeypressRef.current || isEscOwned?.()) return;
99283
100033
  if (key.escape && abortRef.current) abortRef.current.abort();
99284
100034
  }, { isActive: Boolean(isRawModeSupported) });
99285
- const createToolset = (0, import_react28.useCallback)(
100035
+ const createToolset = (0, import_react30.useCallback)(
99286
100036
  () => createLocalToolset({
99287
100037
  cwd: process.cwd(),
99288
100038
  permissions,
99289
100039
  onTodos: setTodos,
99290
- confirm: (req) => new Promise(
99291
- (resolve2) => setPendingConfirm({
100040
+ confirm: (req) => new Promise((resolve2) => {
100041
+ const item = {
100042
+ id: confirmIdRef.current++,
99292
100043
  req,
99293
100044
  resolve: (decision) => {
99294
100045
  answeredThisKeypressRef.current = true;
99295
100046
  setTimeout(() => {
99296
100047
  answeredThisKeypressRef.current = false;
99297
100048
  }, 0);
99298
- setPendingConfirm(null);
100049
+ dequeueConfirm(item);
99299
100050
  resolve2(decision);
99300
100051
  }
99301
- })
99302
- )
100052
+ };
100053
+ enqueueConfirm(item);
100054
+ })
99303
100055
  }),
99304
- [permissions, setPendingConfirm]
100056
+ [permissions, enqueueConfirm, dequeueConfirm]
99305
100057
  );
99306
- const persist = (0, import_react28.useCallback)(
100058
+ const persist = (0, import_react30.useCallback)(
99307
100059
  async (messages, tokens) => {
99308
100060
  if (!sessions) return;
99309
100061
  try {
99310
100062
  const id = sessionIdRef.current ??= sessions.newId();
99311
100063
  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 });
100064
+ await sessions.save({ meta: { v: 1, id, projectRoot, createdAt, updatedAt: createdAt, tokens, title: titleFromMessages(messages), turns: countTurns(messages), todos: todosRef.current }, messages });
99313
100065
  } catch (err) {
99314
- notice(`Session not saved: ${err instanceof Error ? err.message : String(err)}`);
100066
+ noticeError(describeError("session-save", err instanceof Error ? err.message : String(err)));
99315
100067
  }
99316
100068
  },
99317
- [sessions, projectRoot, notice]
100069
+ [sessions, projectRoot, noticeError]
99318
100070
  );
99319
- const applySession = (0, import_react28.useCallback)(
100071
+ const applySession = (0, import_react30.useCallback)(
99320
100072
  (loaded) => {
100073
+ retryRef.current = null;
100074
+ for (const w2 of loaded.warnings ?? []) notice(w2);
99321
100075
  sessionIdRef.current = loaded.meta.id;
99322
100076
  sessionCreatedAtRef.current = loaded.meta.createdAt;
99323
100077
  setHistory(loaded.messages);
99324
100078
  setSessionTokens(loaded.meta.tokens);
99325
100079
  setTurns([]);
99326
100080
  setEvents([]);
99327
- notice(`Resumed "${loaded.meta.title}" (${loaded.messages.length} messages, saved ${formatAge(loaded.meta.updatedAt, /* @__PURE__ */ new Date())}). Earlier output is not replayed.`);
100081
+ setReplayed(replayTurns(loaded.messages));
100082
+ setTodos(loaded.meta.todos ?? []);
100083
+ 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
100084
  },
99329
- [notice]
100085
+ [notice, setTodos]
99330
100086
  );
99331
- (0, import_react28.useEffect)(() => {
100087
+ (0, import_react30.useEffect)(() => {
99332
100088
  if (!resumeLatest2) return;
99333
100089
  if (!sessions) {
99334
100090
  notice("Cannot continue: session storage is not available.");
@@ -99349,7 +100105,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99349
100105
  }
99350
100106
  })();
99351
100107
  }, [resumeLatest2, sessions, projectRoot, notice, applySession]);
99352
- const submit = (0, import_react28.useCallback)(async (prompt) => {
100108
+ const submit = (0, import_react30.useCallback)(async (prompt) => {
99353
100109
  const shellCommand = shellCommandOf(prompt);
99354
100110
  if (shellCommand !== null) {
99355
100111
  if (shellCommand === "") {
@@ -99379,8 +100135,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99379
100135
  } finally {
99380
100136
  commitTurn(prompt, shellEvents, false);
99381
100137
  setEvents([]);
99382
- pendingRef.current?.resolve(false);
99383
- setPendingConfirm(null);
100138
+ refuseAllConfirms();
99384
100139
  setIsRunning(false);
99385
100140
  }
99386
100141
  return;
@@ -99400,6 +100155,8 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99400
100155
  setSessionTokens({ input: 0, output: 0 });
99401
100156
  setTurnTokens(null);
99402
100157
  setTodos([]);
100158
+ setReplayed([]);
100159
+ retryRef.current = null;
99403
100160
  sessionIdRef.current = null;
99404
100161
  sessionCreatedAtRef.current = null;
99405
100162
  return;
@@ -99437,6 +100194,10 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99437
100194
  if (parsed.kind === "list") {
99438
100195
  const { sessions: all, errors } = await sessions.list();
99439
100196
  resumeListRef.current = all;
100197
+ if (onViewResume) {
100198
+ onViewResume({ sessions: all, errors: errors.map((e) => e.message) });
100199
+ return;
100200
+ }
99440
100201
  for (const e of errors) notice(e.message);
99441
100202
  notice(formatSessionList(all, projectRoot, /* @__PURE__ */ new Date()));
99442
100203
  return;
@@ -99517,6 +100278,10 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
99517
100278
  }
99518
100279
  return;
99519
100280
  }
100281
+ case "turns":
100282
+ if (onViewTurns) onViewTurns();
100283
+ else notice("The turn viewer is not available here.");
100284
+ return;
99520
100285
  case "tools": {
99521
100286
  const tools = toolsetRef.current?.tools ?? createLocalToolset({ cwd: process.cwd(), permissions, onTodos: setTodos }).tools;
99522
100287
  notice(`Tools:
@@ -99529,6 +100294,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99529
100294
  return;
99530
100295
  }
99531
100296
  let apiKey;
100297
+ retryRef.current = null;
99532
100298
  try {
99533
100299
  apiKey = await resolveApiKey({
99534
100300
  env: process.env,
@@ -99544,11 +100310,12 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99544
100310
  return;
99545
100311
  }
99546
100312
  if (!apiKey) {
100313
+ retryRef.current = prompt;
99547
100314
  commitTurn(prompt, [{
99548
100315
  type: "error",
99549
100316
  seq: 1,
99550
100317
  message: MISSING_KEY_MESSAGE
99551
- }], false);
100318
+ }], false, void 0, true);
99552
100319
  return;
99553
100320
  }
99554
100321
  setIsRunning(true);
@@ -99562,6 +100329,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99562
100329
  setTurnStartedAt(startedAt);
99563
100330
  let usage = { input: 0, output: 0 };
99564
100331
  let failed = false;
100332
+ let keepForRetry = false;
99565
100333
  try {
99566
100334
  const toolset = createToolset();
99567
100335
  toolsetRef.current = toolset;
@@ -99585,13 +100353,17 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99585
100353
  const attached = await mentions.attach(prompt);
99586
100354
  if (attached.notices.length > 0) setNotices(attached.notices);
99587
100355
  const result = await runAgentLoop(attached.prompt, config, toolset.executor, emit, controller.signal, history);
99588
- setHistory(result.messages);
100356
+ const failure = [...turnEvents].reverse().find((e) => e.type === "error");
100357
+ const noProgress = result.messages.length === history.length + 1;
100358
+ keepForRetry = !controller.signal.aborted && failure !== void 0 && isRetryable(failure.message) && noProgress;
100359
+ const savedMessages = keepForRetry ? history : result.messages;
100360
+ setHistory(savedMessages);
99589
100361
  usage = { input: result.totalInputTokens, output: result.totalOutputTokens };
99590
100362
  failed = !result.success;
99591
100363
  setTurnTokens(usage);
99592
100364
  const totals = { input: sessionTokensRef.current.input + usage.input, output: sessionTokensRef.current.output + usage.output };
99593
100365
  setSessionTokens(totals);
99594
- await persist(result.messages, totals);
100366
+ if (savedMessages.length > 0) await persist(savedMessages, totals);
99595
100367
  } catch (err) {
99596
100368
  failed = true;
99597
100369
  console.error("[agent-ui] Agent loop failed:", err);
@@ -99603,21 +100375,23 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99603
100375
  } finally {
99604
100376
  const toolCount = turnEvents.filter((e) => e.type === "tool_call").length;
99605
100377
  const committedEvents = turnEvents.filter((e) => e.type !== "status");
100378
+ const retained = keepForRetry;
100379
+ retryRef.current = retained ? prompt : null;
99606
100380
  commitTurn(
99607
100381
  prompt,
99608
100382
  committedEvents,
99609
100383
  controller.signal.aborted,
99610
- formatTurnSummary({ toolCount, elapsedMs: Date.now() - startedAt, inputTokens: usage.input, outputTokens: usage.output, interrupted: controller.signal.aborted, failed })
100384
+ formatTurnSummary({ toolCount, elapsedMs: Date.now() - startedAt, inputTokens: usage.input, outputTokens: usage.output, interrupted: controller.signal.aborted, failed }, activeGlyphs().rail === "\u2502"),
100385
+ retained
99611
100386
  );
99612
100387
  setTurnStartedAt(null);
99613
100388
  setEvents([]);
99614
- pendingRef.current?.resolve(false);
99615
- setPendingConfirm(null);
100389
+ refuseAllConfirms();
99616
100390
  abortRef.current = null;
99617
100391
  setIsRunning(false);
99618
100392
  }
99619
- }, [repoName2, history, exit, permissions, notice, setPendingConfirm, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model]);
99620
- const approxTokens = (0, import_react28.useMemo)(
100393
+ }, [repoName2, history, exit, permissions, notice, refuseAllConfirms, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model, onViewTurns, onViewResume, setTodos]);
100394
+ const approxTokens = (0, import_react30.useMemo)(
99621
100395
  () => events.reduce((n, e) => n + (e.type === "text" ? estimateTokens(e.content) : e.type === "text_delta" ? estimateTokens(e.text) : 0), 0),
99622
100396
  [events]
99623
100397
  );
@@ -99628,17 +100402,47 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
99628
100402
  approxTokens,
99629
100403
  ...model ? { model } : {}
99630
100404
  };
99631
- return { turns, events, isRunning, turnTokens, notices, pending, mode, todos, stats, submit, mentions };
100405
+ const retry = (0, import_react30.useCallback)(() => {
100406
+ const kept = retryRef.current;
100407
+ if (kept !== null) void submit(kept);
100408
+ }, [submit]);
100409
+ const resume = (0, import_react30.useMemo)(
100410
+ () => ({
100411
+ available: Boolean(sessions),
100412
+ load: async (meta) => {
100413
+ if (!sessions) throw new Error("Session storage is not available.");
100414
+ applySession(await sessions.load(meta.id));
100415
+ },
100416
+ remove: async (meta) => {
100417
+ if (!sessions) throw new Error("Session storage is not available.");
100418
+ await sessions.delete(meta.id);
100419
+ resumeListRef.current = resumeListRef.current.filter((m2) => m2.id !== meta.id);
100420
+ if (sessionIdRef.current === meta.id) {
100421
+ sessionIdRef.current = null;
100422
+ sessionCreatedAtRef.current = null;
100423
+ }
100424
+ },
100425
+ preview: async (meta) => {
100426
+ if (!sessions) return null;
100427
+ return sessions.preview(meta.id);
100428
+ }
100429
+ }),
100430
+ [sessions, applySession]
100431
+ );
100432
+ return { turns, events, isRunning, turnTokens, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume };
99632
100433
  }
99633
- var import_react28, apiUrl;
100434
+ var import_react30, apiUrl;
99634
100435
  var init_useReplController = __esm({
99635
100436
  async "src/useReplController.ts"() {
99636
100437
  "use strict";
99637
- import_react28 = __toESM(require_react(), 1);
100438
+ import_react30 = __toESM(require_react(), 1);
99638
100439
  await init_build2();
99639
100440
  init_src2();
99640
100441
  init_footer();
99641
100442
  init_session_store();
100443
+ init_replay();
100444
+ init_rail();
100445
+ init_describe_error();
99642
100446
  init_compact();
99643
100447
  init_models();
99644
100448
  init_session_commands();
@@ -99651,48 +100455,373 @@ var init_useReplController = __esm({
99651
100455
  }
99652
100456
  });
99653
100457
 
100458
+ // src/components/TurnViewer.tsx
100459
+ function TurnViewer({ turns, steps, rows, columns, onClose, onEscape }) {
100460
+ const { isRawModeSupported } = use_stdin_default();
100461
+ const glyphs = activeGlyphs();
100462
+ const [turnIdx, setTurnIdx] = (0, import_react31.useState)(null);
100463
+ const [cursorRaw, setCursor] = (0, import_react31.useState)(turns.length - 1);
100464
+ const cursor = clamp(cursorRaw, 0, Math.max(0, turns.length - 1));
100465
+ const [stepCursor, setStepCursor] = (0, import_react31.useState)(0);
100466
+ const [openId, setOpenId] = (0, import_react31.useState)(null);
100467
+ const [scroll, setScroll] = (0, import_react31.useState)(0);
100468
+ const turn = turnIdx === null ? null : turns[turnIdx] ?? null;
100469
+ const turnSteps = turn ? steps.get(turn.id) ?? [] : [];
100470
+ const detailRows = Math.max(3, rows - 6 - turnSteps.length);
100471
+ use_input_default((_input, key) => {
100472
+ if (key.escape) onEscape?.();
100473
+ if (turn === null) {
100474
+ if (key.escape) onClose();
100475
+ else if (key.upArrow) setCursor((c) => clamp(clamp(c, 0, turns.length - 1) - 1, 0, turns.length - 1));
100476
+ else if (key.downArrow) setCursor((c) => clamp(clamp(c, 0, turns.length - 1) + 1, 0, turns.length - 1));
100477
+ else if (key.return && turns.length > 0) {
100478
+ setTurnIdx(cursor);
100479
+ setStepCursor(0);
100480
+ setOpenId(null);
100481
+ setScroll(0);
100482
+ }
100483
+ return;
100484
+ }
100485
+ if (key.escape) {
100486
+ if (openId !== null) {
100487
+ setOpenId(null);
100488
+ setScroll(0);
100489
+ } else setTurnIdx(null);
100490
+ } else if (key.upArrow) {
100491
+ setStepCursor((c) => clamp(c - 1, 0, turnSteps.length - 1));
100492
+ setOpenId(null);
100493
+ setScroll(0);
100494
+ } else if (key.downArrow) {
100495
+ setStepCursor((c) => clamp(c + 1, 0, turnSteps.length - 1));
100496
+ setOpenId(null);
100497
+ setScroll(0);
100498
+ } else if (key.return && turnSteps.length > 0) {
100499
+ const id = turnSteps[stepCursor].id;
100500
+ setOpenId((cur) => cur === id ? null : id);
100501
+ setScroll(0);
100502
+ } else if (key.pageDown) setScroll((s) => s + detailRows);
100503
+ else if (key.pageUp) setScroll((s) => Math.max(0, s - detailRows));
100504
+ }, { isActive: Boolean(isRawModeSupported) });
100505
+ const listRows = Math.max(3, rows - 4);
100506
+ const listStart = clamp(cursor - listRows + 1, 0, Math.max(0, turns.length - listRows));
100507
+ if (turn === null) {
100508
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
100509
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { bold: true, children: [
100510
+ "Earlier turns ",
100511
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: "(\u2191\u2193 choose \xB7 enter open \xB7 esc close)" })
100512
+ ] }),
100513
+ turns.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: "No finished turns yet." }),
100514
+ turns.slice(listStart, listStart + listRows).map((t, k2) => {
100515
+ const idx = listStart + k2;
100516
+ const n = steps.get(t.id)?.length ?? 0;
100517
+ const prompt = t.prompt.split("\n")[0].slice(0, Math.max(10, columns - 24));
100518
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { inverse: idx === cursor, children: [
100519
+ idx === cursor ? "> " : " ",
100520
+ idx + 1,
100521
+ ". ",
100522
+ prompt,
100523
+ " ",
100524
+ glyphs.sep,
100525
+ " ",
100526
+ n,
100527
+ " step",
100528
+ n === 1 ? "" : "s"
100529
+ ] }, t.id);
100530
+ })
100531
+ ] });
100532
+ }
100533
+ const open3 = turnSteps.find((r) => r.id === openId);
100534
+ const win = open3 ? detailWindow(open3.detail, detailRows, Math.max(20, columns - 8), scroll) : null;
100535
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
100536
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: `${turnIdx + 1}. ${turn.prompt.split("\n")[0].slice(0, Math.max(10, columns - 8))}` }),
100537
+ /* @__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" }),
100538
+ turnSteps.map((r, i) => {
100539
+ const line = fitStepLine({ verb: r.verb, target: r.target, meta: r.meta || void 0, columns, indent: 6, ellipsis: glyphs.ellipsis, sep: glyphs.sep });
100540
+ const marker = glyphs.marker[r.state];
100541
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
100542
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { inverse: i === stepCursor, children: [
100543
+ i === stepCursor ? "> " : " ",
100544
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint(r.state === "failed" ? "delete" : "add"), children: marker }),
100545
+ " ",
100546
+ line.verb,
100547
+ " ",
100548
+ line.target,
100549
+ line.meta ? ` ${glyphs.sep} ${line.meta}` : ""
100550
+ ] }),
100551
+ r.id === openId && win && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", marginLeft: 4, children: [
100552
+ win.above > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: `${glyphs.ellipsis} ${win.above} line${win.above === 1 ? "" : "s"} above` }),
100553
+ win.lines.map((l, k2) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: l }, k2)),
100554
+ win.below > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { ...paint("secondary"), children: `${glyphs.ellipsis} ${win.below} more line${win.below === 1 ? "" : "s"}` })
100555
+ ] })
100556
+ ] }, r.id);
100557
+ })
100558
+ ] });
100559
+ }
100560
+ var import_react31, import_jsx_runtime10, clamp;
100561
+ var init_TurnViewer = __esm({
100562
+ async "src/components/TurnViewer.tsx"() {
100563
+ "use strict";
100564
+ import_react31 = __toESM(require_react(), 1);
100565
+ await init_build2();
100566
+ init_step_view();
100567
+ init_rail();
100568
+ init_theme();
100569
+ import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
100570
+ clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
100571
+ }
100572
+ });
100573
+
100574
+ // src/render/resume-rows.ts
100575
+ import path9 from "node:path";
100576
+ function orderSessions(sessions, projectRoot) {
100577
+ return [...sessions.filter((s) => s.projectRoot === projectRoot), ...sessions.filter((s) => s.projectRoot !== projectRoot)];
100578
+ }
100579
+ function formatResumeRow(meta, now, columns, sep2 = "\xB7", ellipsis = "\u2026") {
100580
+ const project = path9.basename(meta.projectRoot) || meta.projectRoot;
100581
+ 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} `);
100582
+ const head = `${formatAge(meta.updatedAt, now)} ${sep2} ${project} ${sep2} `;
100583
+ const fixed = head.length + 2 + ` ${sep2} ${tail}`.length;
100584
+ const room = Math.max(8, columns - fixed);
100585
+ const title = meta.title.length > room ? `${meta.title.slice(0, Math.max(1, room - ellipsis.length))}${ellipsis}` : meta.title;
100586
+ return `${head}"${title}" ${sep2} ${tail}`;
100587
+ }
100588
+ var init_resume_rows = __esm({
100589
+ "src/render/resume-rows.ts"() {
100590
+ "use strict";
100591
+ init_footer();
100592
+ init_session_store();
100593
+ }
100594
+ });
100595
+
100596
+ // src/components/ResumePicker.tsx
100597
+ function ResumePicker({ sessions, errors, projectRoot, now, rows, columns, preview, onResume, onDelete, onClose, onEscape }) {
100598
+ const { isRawModeSupported } = use_stdin_default();
100599
+ const glyphs = activeGlyphs();
100600
+ const [items, setItems] = (0, import_react32.useState)(() => orderSessions(sessions, projectRoot));
100601
+ const [cursor, setCursor] = (0, import_react32.useState)(0);
100602
+ const [stage, setStage] = (0, import_react32.useState)("browse");
100603
+ const [message, setMessage] = (0, import_react32.useState)(null);
100604
+ const [previews, setPreviews] = (0, import_react32.useState)({});
100605
+ const at = clamp2(cursor, 0, Math.max(0, items.length - 1));
100606
+ const current = items[at];
100607
+ (0, import_react32.useEffect)(() => {
100608
+ if (!current || current.id in previews) return;
100609
+ let live = true;
100610
+ 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)) })));
100611
+ return () => {
100612
+ live = false;
100613
+ };
100614
+ }, [current, preview, previews]);
100615
+ use_input_default((input, key) => {
100616
+ if (key.escape) {
100617
+ onEscape?.();
100618
+ if (stage !== "browse") {
100619
+ setStage("browse");
100620
+ setMessage(null);
100621
+ } else onClose();
100622
+ return;
100623
+ }
100624
+ if (stage === "confirm-delete") {
100625
+ if (input === "y" && current) {
100626
+ onDelete(current).then(() => {
100627
+ setItems((list2) => list2.filter((m2) => m2.id !== current.id));
100628
+ setMessage(`Deleted "${current.title}".`);
100629
+ }).catch((err) => setMessage(`Could not delete: ${err instanceof Error ? err.message : String(err)}`));
100630
+ setStage("browse");
100631
+ } else if (input === "n") {
100632
+ setStage("browse");
100633
+ setMessage(null);
100634
+ }
100635
+ return;
100636
+ }
100637
+ if (key.upArrow) {
100638
+ setCursor((c) => clamp2(c - 1, 0, items.length - 1));
100639
+ setStage("browse");
100640
+ setMessage(null);
100641
+ } else if (key.downArrow) {
100642
+ setCursor((c) => clamp2(c + 1, 0, items.length - 1));
100643
+ setStage("browse");
100644
+ setMessage(null);
100645
+ } else if (input === "d" && current) {
100646
+ setStage("confirm-delete");
100647
+ setMessage(`Delete "${current.title}"? y/n`);
100648
+ } else if (key.return && current) {
100649
+ if (current.projectRoot !== projectRoot && stage !== "confirm-foreign") {
100650
+ setStage("confirm-foreign");
100651
+ setMessage(`That session belongs to ${current.projectRoot}, not ${projectRoot}. Enter again to load it anyway, Esc to go back.`);
100652
+ return;
100653
+ }
100654
+ onResume(current).then(onClose).catch((err) => setMessage(`Could not resume: ${err instanceof Error ? err.message : String(err)}`));
100655
+ }
100656
+ }, { isActive: Boolean(isRawModeSupported) });
100657
+ const listRows = Math.max(3, rows - 8);
100658
+ const start = clamp2(at - listRows + 1, 0, Math.max(0, items.length - listRows));
100659
+ const shown = (0, import_react32.useMemo)(() => items.slice(start, start + listRows), [items, start, listRows]);
100660
+ const pv = current ? previews[current.id] : void 0;
100661
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
100662
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { bold: true, children: [
100663
+ "Resume a session ",
100664
+ /* @__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)` })
100665
+ ] }),
100666
+ errors.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("needsYou"), children: e }, i)),
100667
+ items.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("secondary"), children: "No saved sessions yet." }),
100668
+ shown.map((m2, k2) => {
100669
+ const selected = start + k2 === at;
100670
+ const foreign = m2.projectRoot !== projectRoot;
100671
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { inverse: selected, ...foreign && !selected ? paint("secondary") : {}, children: [
100672
+ selected ? "> " : " ",
100673
+ formatResumeRow(m2, now, columns - 4, glyphs.sep, glyphs.ellipsis),
100674
+ foreign ? ` [${m2.projectRoot}]` : ""
100675
+ ] }, m2.id);
100676
+ }),
100677
+ 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}"` }),
100678
+ message && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { ...paint("needsYou"), children: message })
100679
+ ] });
100680
+ }
100681
+ var import_react32, import_jsx_runtime11, clamp2;
100682
+ var init_ResumePicker = __esm({
100683
+ async "src/components/ResumePicker.tsx"() {
100684
+ "use strict";
100685
+ import_react32 = __toESM(require_react(), 1);
100686
+ await init_build2();
100687
+ init_resume_rows();
100688
+ init_rail();
100689
+ init_theme();
100690
+ import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
100691
+ clamp2 = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
100692
+ }
100693
+ });
100694
+
99654
100695
  // src/App.tsx
99655
100696
  var App_exports = {};
99656
100697
  __export(App_exports, {
99657
100698
  App: () => App2
99658
100699
  });
99659
100700
  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"))),
100701
+ import path10 from "node:path";
100702
+ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2, yolo: yolo2, history, sessions, resumeLatest: resumeLatest2, git: gitRunner, interactivePicker = true }) {
100703
+ const promptHistory = (0, import_react33.useMemo)(
100704
+ () => history ?? new PromptHistory(createFileHistoryIO(path10.join(os4.homedir(), ".spacecode", "history"))),
99664
100705
  [history]
99665
100706
  );
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 });
100707
+ const sessionStore = (0, import_react33.useMemo)(() => sessions ?? new SessionStore(path10.join(os4.homedir(), ".spacecode", "sessions")), [sessions]);
100708
+ const runGit = (0, import_react33.useMemo)(() => gitRunner ?? createNodeGitRunner(process.cwd()), [gitRunner]);
100709
+ const startedAtRef = (0, import_react33.useRef)(Date.now());
100710
+ const [viewingRaw, setViewing] = (0, import_react33.useState)(false);
100711
+ const escViewRef = (0, import_react33.useRef)(false);
100712
+ const escSwallowRef = (0, import_react33.useRef)(false);
100713
+ const swallowEsc = (0, import_react33.useCallback)(() => {
100714
+ escSwallowRef.current = true;
100715
+ setTimeout(() => {
100716
+ escSwallowRef.current = false;
100717
+ }, 0);
100718
+ }, []);
100719
+ const isEscOwned = (0, import_react33.useCallback)(() => escViewRef.current || escSwallowRef.current, []);
100720
+ const openViewer = (0, import_react33.useCallback)(() => setViewing(true), []);
100721
+ const { isRawModeSupported } = use_stdin_default();
100722
+ const [resumeFound, setResumeFound] = (0, import_react33.useState)(null);
100723
+ const openResume = (0, import_react33.useCallback)((found) => setResumeFound(found), []);
100724
+ const { turns, events, isRunning, 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
100725
  const { columns, rows } = useTerminalSize();
100726
+ const viewing = viewingRaw && !pending;
100727
+ const picking = resumeFound !== null && !pending;
100728
+ const liveSteps = (0, import_react33.useMemo)(() => stepsOfEvents(events), [events]);
100729
+ const [focusId, setFocusId] = (0, import_react33.useState)(null);
100730
+ const [openId, setOpenId] = (0, import_react33.useState)(null);
100731
+ const focusIdx = focusId === null ? -1 : liveSteps.findIndex((r) => r.id === focusId);
100732
+ const focusing = focusIdx >= 0 && !viewing && !picking && !pending;
100733
+ escViewRef.current = focusing || viewing || picking;
100734
+ const enterFocus = (0, import_react33.useCallback)(() => {
100735
+ if (liveSteps.length > 0) setFocusId(liveSteps[liveSteps.length - 1].id);
100736
+ }, [liveSteps]);
100737
+ use_input_default((input, key) => {
100738
+ if (key.escape) {
100739
+ swallowEsc();
100740
+ setFocusId(null);
100741
+ setOpenId(null);
100742
+ } else if (key.tab && !key.shift || key.downArrow) {
100743
+ setFocusId(liveSteps[(focusIdx + 1) % liveSteps.length].id);
100744
+ setOpenId(null);
100745
+ } else if (key.upArrow) {
100746
+ setFocusId(liveSteps[(focusIdx + liveSteps.length - 1) % liveSteps.length].id);
100747
+ setOpenId(null);
100748
+ } else if (key.return || input === " ") {
100749
+ setOpenId((cur) => cur === focusId ? null : focusId);
100750
+ }
100751
+ }, { isActive: Boolean(isRawModeSupported) && focusing });
100752
+ use_input_default((input, key) => {
100753
+ if (key.ctrl && input === "o") setViewing(true);
100754
+ }, { isActive: Boolean(isRawModeSupported) && !viewing && !picking && !pending });
100755
+ const waitingStep = pending ? findBlockedStep(liveSteps, pending.req) : void 0;
100756
+ const promptNode = pending ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PermissionPrompt, { req: pending.req, onDecide: pending.resolve }, pending.id) : null;
100757
+ const viewerTurns = (0, import_react33.useMemo)(() => [...replayed, ...turns], [replayed, turns]);
100758
+ const turnSteps = (0, import_react33.useMemo)(() => viewing ? stepsOfTurns(viewerTurns) : /* @__PURE__ */ new Map(), [viewing, viewerTurns]);
100759
+ const [gitState, setGitState] = (0, import_react33.useState)(null);
100760
+ (0, import_react33.useEffect)(() => {
100761
+ let live = true;
100762
+ void readGitState(runGit).then((g2) => {
100763
+ if (live) setGitState(g2);
100764
+ });
100765
+ return () => {
100766
+ live = false;
100767
+ };
100768
+ }, [runGit, turns.length]);
99669
100769
  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: [
100770
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", children: [
100771
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Header, { project: repoName2 ?? path10.basename(process.cwd()), git: gitState, turns: turns.length, elapsedMs: Date.now() - startedAtRef.current, columns }),
100772
+ 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)),
100773
+ /* @__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: [
100774
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { ...paint("secondary"), children: [
99674
100775
  "> ",
99675
100776
  turn.prompt
99676
100777
  ] }),
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 })
100778
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EventRenderer, { events: turn.events, promptSaved: turn.retained }),
100779
+ turn.interrupted && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("needsYou"), children: "Interrupted" }),
100780
+ turn.summary && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("secondary"), children: turn.summary })
99680
100781
  ] }, 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 })
100782
+ viewing && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TurnViewer, { turns: viewerTurns, steps: turnSteps, rows, columns, onClose: () => setViewing(false), onEscape: swallowEsc }),
100783
+ picking && resumeFound && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
100784
+ ResumePicker,
100785
+ {
100786
+ sessions: resumeFound.sessions,
100787
+ errors: resumeFound.errors,
100788
+ projectRoot: process.cwd(),
100789
+ now: /* @__PURE__ */ new Date(),
100790
+ rows,
100791
+ columns,
100792
+ preview: resume.preview,
100793
+ onResume: resume.load,
100794
+ onDelete: resume.remove,
100795
+ onClose: () => setResumeFound(null),
100796
+ onEscape: swallowEsc
100797
+ }
100798
+ ),
100799
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", display: viewing || picking ? "none" : "flex", children: [
100800
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(EventRenderer, { deferErrors: true, events, liveMaxLines: Math.max(6, rows - 12), focusId: focusing ? focusId : null, openId: focusing ? openId : null, detailRows: Math.max(4, rows - 14), waitingId: waitingStep?.id ?? null, prompt: promptNode }),
100801
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TodoChecklist, { todos }),
100802
+ pending && !waitingStep && promptNode,
100803
+ isRunning && !pending && stats.elapsedMs !== null && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("secondary"), children: formatThinking(stats.elapsedMs, stats.approxTokens, activeGlyphs().rail === "\u2502") }),
100804
+ 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" }),
100805
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PromptEditor, { onEmptyEnter: isRunning ? void 0 : retry, promptGlyph: promptGlyph(mode, activeGlyphs().rail === "\u2502"), onSubmit: submit, disabled: isRunning, history: promptHistory, listFiles: mentions.list, onTab: enterFocus, locked: focusing || viewing || picking || Boolean(pending) }),
100806
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...footer.level === "critical" ? paint("delete") : footer.level === "warn" ? paint("needsYou") : paint("secondary"), children: footer.text }),
100807
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { ...paint("secondary"), children: formatHints({ running: isRunning, asking: Boolean(pending), columns }, activeGlyphs().sep) })
100808
+ ] })
99687
100809
  ] });
99688
100810
  }
99689
- var import_react29, import_jsx_runtime8, BANNER;
100811
+ var import_react33, import_jsx_runtime12, BANNER;
99690
100812
  var init_App2 = __esm({
99691
100813
  async "src/App.tsx"() {
99692
100814
  "use strict";
99693
- import_react29 = __toESM(require_react(), 1);
100815
+ import_react33 = __toESM(require_react(), 1);
99694
100816
  await init_build2();
99695
100817
  await init_WelcomeBanner();
100818
+ await init_Header();
100819
+ await init_ErrorBlock();
100820
+ init_git_status();
100821
+ init_node_git();
100822
+ init_hints();
100823
+ init_mode_line();
100824
+ init_rail();
99696
100825
  await init_EventRenderer();
99697
100826
  await init_PermissionPrompt();
99698
100827
  await init_TodoChecklist();
@@ -99703,7 +100832,11 @@ var init_App2 = __esm({
99703
100832
  await init_useReplController();
99704
100833
  await init_useTerminalSize();
99705
100834
  init_session_store();
99706
- import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
100835
+ init_theme();
100836
+ await init_TurnViewer();
100837
+ await init_ResumePicker();
100838
+ init_step_view();
100839
+ import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
99707
100840
  BANNER = /* @__PURE__ */ Symbol("welcome-banner");
99708
100841
  }
99709
100842
  });
@@ -99724,7 +100857,7 @@ function hideCiVariables(env3, stdoutIsTTY) {
99724
100857
  }
99725
100858
 
99726
100859
  // src/index.tsx
99727
- var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
100860
+ var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
99728
100861
  var repoName = process.env.REPO_NAME || process.cwd().split("/").pop() || void 0;
99729
100862
  var validModes = ["guest", "new_project", "existing_repo"];
99730
100863
  var rawMode = process.env.ONBOARDING_MODE;
@@ -99741,7 +100874,7 @@ try {
99741
100874
  } finally {
99742
100875
  restoreCiVariables();
99743
100876
  }
99744
- render2(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(App3, { repoName, onboardingMode, launchCount, yolo, resumeLatest }));
100877
+ render2(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(App3, { repoName, onboardingMode, launchCount, yolo, resumeLatest, interactivePicker: Boolean(process.stdout.isTTY) }));
99745
100878
  /*! Bundled license information:
99746
100879
 
99747
100880
  react/cjs/react.production.min.js: