@cryer/star-cli 0.3.10 → 0.3.11

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.
@@ -6,6 +6,7 @@ import {
6
6
  buildDisplayMessages,
7
7
  clearSessions,
8
8
  computeCostUsd,
9
+ deleteSession,
9
10
  estimateCost,
10
11
  folderIcon,
11
12
  formatByteCount,
@@ -19,6 +20,7 @@ import {
19
20
  previewLines,
20
21
  relativeTime,
21
22
  renderMarkdown,
23
+ resolveIconMode,
22
24
  resolveMentions,
23
25
  resolveSessionId,
24
26
  sessionPreview,
@@ -28,7 +30,7 @@ import {
28
30
  thinkingIcon,
29
31
  toTerminalSafe,
30
32
  toolIcon
31
- } from "./chunk-PDFC7QVW.js";
33
+ } from "./chunk-GYXF6NPC.js";
32
34
  import {
33
35
  AgentLoop,
34
36
  WORKING_DIFF_MAX_LINES,
@@ -45,14 +47,14 @@ import {
45
47
  isDangerousCommand,
46
48
  isGitRepo,
47
49
  summarizeMessages
48
- } from "./chunk-MNVCCOI2.js";
50
+ } from "./chunk-63CGUF3F.js";
49
51
  import {
50
52
  contextWindowTokens,
51
53
  coreMessageText,
52
54
  diffTreeNames,
53
55
  reconcileStarMessages,
54
56
  toCoreMessages
55
- } from "./chunk-IJGUW4PM.js";
57
+ } from "./chunk-N4MIQSNZ.js";
56
58
  import {
57
59
  createModel,
58
60
  reasoningEffortMetadata
@@ -66,7 +68,7 @@ import {
66
68
  formatTaskFinished,
67
69
  formatTaskList,
68
70
  formatTaskStarted
69
- } from "./chunk-URC7JXKX.js";
71
+ } from "./chunk-Q5FJN4LN.js";
70
72
  import {
71
73
  MAX_IMAGE_DIMENSION,
72
74
  SKIP_DIRS,
@@ -95,7 +97,7 @@ import {
95
97
  starHome,
96
98
  upsertEnvContent,
97
99
  userMemoryPath
98
- } from "./chunk-A5PXPWHB.js";
100
+ } from "./chunk-LSYGHLRU.js";
99
101
 
100
102
  // src/cli/repl.tsx
101
103
  import { Box as Box13, Text as Text14, render, useApp } from "ink";
@@ -662,7 +664,7 @@ async function compactSession(opts) {
662
664
  return {
663
665
  message: `Compacted context: ${messages.length} -> ${next.length} messages (~${formatTokens(beforeTokens)} -> ~${formatTokens(afterTokens)} estimated tokens).`,
664
666
  compacted: true,
665
- messages: coreNext
667
+ messages: next
666
668
  };
667
669
  }
668
670
  async function applyCompactionSummary(original, compacted, config, model, temperature, signal) {
@@ -2261,7 +2263,7 @@ function filterCommands(input, commands) {
2261
2263
  const fuzzy = commands.filter(
2262
2264
  (cmd) => !cmd.name.toLowerCase().startsWith(prefix) && isSubsequence(prefix, cmd.name.toLowerCase())
2263
2265
  ).sort(byName);
2264
- return [...prefixed, ...fuzzy].slice(0, MAX_SUGGESTIONS);
2266
+ return [...prefixed, ...fuzzy];
2265
2267
  }
2266
2268
  function didYouMeanSuffix(names) {
2267
2269
  if (names.length === 0) return "";
@@ -2281,7 +2283,7 @@ function filterArgHints(input, commands) {
2281
2283
  const candidates = typeof source === "function" ? source(argPrefix) : source;
2282
2284
  const prefix = argPrefix.toLowerCase();
2283
2285
  const stem = input.slice(0, input.length - argPrefix.length);
2284
- return candidates.filter((candidate) => candidate.toLowerCase().startsWith(prefix)).slice(0, MAX_SUGGESTIONS).map((value) => ({ value, replacement: `${stem}${value} ` }));
2286
+ return candidates.filter((candidate) => candidate.toLowerCase().startsWith(prefix)).map((value) => ({ value, replacement: `${stem}${value}` }));
2285
2287
  }
2286
2288
 
2287
2289
  // src/cli/commands/undo.ts
@@ -2607,14 +2609,38 @@ import { Box, Text } from "ink";
2607
2609
  import { useRef as useRef2, useState } from "react";
2608
2610
  import { jsx, jsxs } from "react/jsx-runtime";
2609
2611
  var SELECT_PROMPT_MAX_VISIBLE = 9;
2610
- function SelectPrompt({ title, options, onSelect, onCancel }) {
2612
+ function SelectPrompt({ title, options, onSelect, onCancel, onDelete }) {
2611
2613
  const [index, setIndex] = useState(0);
2612
2614
  const indexRef = useRef2(0);
2615
+ const [confirming, setConfirming] = useState(null);
2616
+ const confirmingRef = useRef2(null);
2617
+ const deletingRef = useRef2(false);
2618
+ const setConfirm = (option) => {
2619
+ confirmingRef.current = option;
2620
+ setConfirming(option);
2621
+ };
2613
2622
  const move = (delta) => {
2614
2623
  indexRef.current = Math.max(0, Math.min(indexRef.current + delta, options.length - 1));
2615
2624
  setIndex(indexRef.current);
2616
2625
  };
2617
2626
  useInput((input, key) => {
2627
+ const pending = confirmingRef.current;
2628
+ if (pending) {
2629
+ if (input === "y" || input === "Y") {
2630
+ setConfirm(null);
2631
+ if (onDelete && !deletingRef.current) {
2632
+ deletingRef.current = true;
2633
+ void onDelete(pending.value).catch(() => options).then((next) => {
2634
+ deletingRef.current = false;
2635
+ indexRef.current = Math.min(indexRef.current, Math.max(0, next.length - 1));
2636
+ setIndex(indexRef.current);
2637
+ });
2638
+ }
2639
+ } else if (input === "n" || input === "N" || key.escape) {
2640
+ setConfirm(null);
2641
+ }
2642
+ return;
2643
+ }
2618
2644
  if (key.escape) {
2619
2645
  onCancel();
2620
2646
  return;
@@ -2624,6 +2650,11 @@ function SelectPrompt({ title, options, onSelect, onCancel }) {
2624
2650
  if (option) onSelect(option.value);
2625
2651
  return;
2626
2652
  }
2653
+ if (onDelete && key.ctrl && input === "x") {
2654
+ const option = options[indexRef.current];
2655
+ if (option) setConfirm(option);
2656
+ return;
2657
+ }
2627
2658
  if (key.downArrow || input === "j") {
2628
2659
  move(1);
2629
2660
  } else if (key.upArrow || input === "k") {
@@ -2665,7 +2696,15 @@ function SelectPrompt({ title, options, onSelect, onCancel }) {
2665
2696
  below,
2666
2697
  " more below"
2667
2698
  ] }),
2668
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[\u2191/\u2193 or j/k] move [Enter] select [Esc] cancel" })
2699
+ confirming ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
2700
+ "Delete ",
2701
+ confirming.label,
2702
+ "? [y] yes [n] no"
2703
+ ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2704
+ "[\u2191/\u2193 or j/k] move [Enter] select",
2705
+ onDelete ? " [Ctrl+X] delete" : "",
2706
+ " [Esc] cancel"
2707
+ ] })
2669
2708
  ] });
2670
2709
  }
2671
2710
 
@@ -2966,7 +3005,7 @@ var InputBox = memo(function InputBox2({
2966
3005
  if (suggestions.length > 0) {
2967
3006
  const cmd = suggestions[activeIndex];
2968
3007
  if (!cmd) return;
2969
- const text = `/${cmd.name} `;
3008
+ const text = `/${cmd.name}`;
2970
3009
  edit(text, text.length);
2971
3010
  return;
2972
3011
  }
@@ -3095,7 +3134,11 @@ ${currentValue.slice(currentCursor)}`,
3095
3134
  );
3096
3135
  return;
3097
3136
  }
3098
- const text = expandPastes(currentValue).trim();
3137
+ let text = expandPastes(currentValue).trim();
3138
+ if (suggestions.length > 0 && text.startsWith("/") && text.length > 1) {
3139
+ const cmd = suggestions[activeIndex];
3140
+ if (cmd) text = `/${cmd.name}`;
3141
+ }
3099
3142
  if (text.length > 0) {
3100
3143
  if (!text.startsWith("/")) setHistory((prev) => [...prev, text]);
3101
3144
  onSubmit(text);
@@ -3219,18 +3262,47 @@ ${value.slice(cursor)}`, cursor + 1);
3219
3262
  const cursorChar = at === "\n" || at === void 0 || at === PASTE_DELIM ? " " : at;
3220
3263
  const tail = (at === "\n" ? "\n" : "") + stylePasteTokens(after);
3221
3264
  const styled = search ? `\x1B[36m(reverse-search) \x1B[39m'${search.query}': ${search.match === null ? "\x1B[2mno match\x1B[22m\x1B[7m \x1B[27m" : `${history[search.match]}\x1B[7m \x1B[27m`}` : `\x1B[36m\u276F \x1B[39m${stylePasteTokens(before)}\x1B[7m${cursorChar}\x1B[27m${tail}`;
3265
+ const menuStart = Math.min(
3266
+ Math.max(0, activeIndex - Math.floor(MAX_SUGGESTIONS / 2)),
3267
+ Math.max(0, suggestionCount - MAX_SUGGESTIONS)
3268
+ );
3269
+ const menuEnd = Math.min(suggestionCount, menuStart + MAX_SUGGESTIONS);
3222
3270
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
3223
3271
  /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { children: styled }) }),
3224
- suggestions.length > 0 && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: suggestions.map((cmd, index) => {
3225
- const label = cmd.usage ?? `/${cmd.name}`;
3226
- return index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: `${label} - ${cmd.description}` }, cmd.name) : /* @__PURE__ */ jsxs2(Text2, { children: [
3227
- /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: label }),
3228
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` - ${cmd.description}` })
3229
- ] }, cmd.name);
3230
- }) }),
3231
- argHints.length > 0 && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: argHints.map(
3232
- (hint, index) => index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: hint.value }, hint.value) : /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: hint.value }, hint.value)
3233
- ) }),
3272
+ suggestions.length > 0 && /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, children: [
3273
+ menuStart > 0 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3274
+ "\u2026 ",
3275
+ menuStart,
3276
+ " more above"
3277
+ ] }),
3278
+ suggestions.slice(menuStart, menuEnd).map((cmd, index) => {
3279
+ const label = cmd.usage ?? `/${cmd.name}`;
3280
+ return menuStart + index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: `${label} - ${cmd.description}` }, cmd.name) : /* @__PURE__ */ jsxs2(Text2, { children: [
3281
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: label }),
3282
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` - ${cmd.description}` })
3283
+ ] }, cmd.name);
3284
+ }),
3285
+ menuEnd < suggestions.length && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3286
+ "\u2026 ",
3287
+ suggestions.length - menuEnd,
3288
+ " more below"
3289
+ ] })
3290
+ ] }),
3291
+ argHints.length > 0 && /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, children: [
3292
+ menuStart > 0 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3293
+ "\u2026 ",
3294
+ menuStart,
3295
+ " more above"
3296
+ ] }),
3297
+ argHints.slice(menuStart, menuEnd).map(
3298
+ (hint, index) => menuStart + index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: hint.value }, hint.value) : /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: hint.value }, hint.value)
3299
+ ),
3300
+ menuEnd < argHints.length && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3301
+ "\u2026 ",
3302
+ argHints.length - menuEnd,
3303
+ " more below"
3304
+ ] })
3305
+ ] }),
3234
3306
  showPathSuggestions && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: pathSuggestions.map(
3235
3307
  (sugg, index) => index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: `@${sugg.path}` }, sugg.path) : /* @__PURE__ */ jsxs2(Text2, { children: [
3236
3308
  /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: `@${sugg.path}` }),
@@ -3600,10 +3672,11 @@ var MessageList = memo2(function MessageList2({ messages }) {
3600
3672
  ] }) }, message.id);
3601
3673
  }
3602
3674
  if (message.role === "user") {
3675
+ const color = message.steer ? "magenta" : "cyan";
3603
3676
  return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginBottom, children: [
3604
3677
  /* @__PURE__ */ jsxs7(Box6, { children: [
3605
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "\u276F " }),
3606
- /* @__PURE__ */ jsxs7(Text7, { color: "cyan", children: [
3678
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color, children: message.steer ? "\xBB " : "\u276F " }),
3679
+ /* @__PURE__ */ jsxs7(Text7, { color, children: [
3607
3680
  message.text,
3608
3681
  message.interrupted && interruptedMarker(message.text)
3609
3682
  ] })
@@ -3815,6 +3888,14 @@ function truncateTail(text, max) {
3815
3888
  if (flat.length <= max) return flat;
3816
3889
  return `\u2026${flat.slice(flat.length - max)}`;
3817
3890
  }
3891
+ function roughReasoningTokens(text) {
3892
+ let weight = 0;
3893
+ for (let i = 0; i < text.length; i++) {
3894
+ const code = text.charCodeAt(i);
3895
+ weight += code >= 12288 && code <= 12543 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519 ? 4 : 1;
3896
+ }
3897
+ return Math.ceil(weight / 4);
3898
+ }
3818
3899
  var ThinkingIndicator = memo5(function ThinkingIndicator2({
3819
3900
  reasoning,
3820
3901
  frame,
@@ -3827,13 +3908,17 @@ var ThinkingIndicator = memo5(function ThinkingIndicator2({
3827
3908
  const spinner = SPINNER_FRAMES[(frame ?? tick) % SPINNER_FRAMES.length];
3828
3909
  const label = activity ?? `${thinkingIcon} star is thinking\u2026`;
3829
3910
  const elapsedSec = startedAt === void 0 ? void 0 : Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
3830
- const elapsed = elapsedSec !== void 0 && elapsedSec >= 3 ? ` (${formatElapsedSeconds(elapsedSec)})` : "";
3911
+ const stats = [];
3912
+ if (elapsedSec !== void 0 && elapsedSec >= 3) stats.push(formatElapsedSeconds(elapsedSec));
3913
+ const thoughtTokens = reasoning ? roughReasoningTokens(reasoning) : 0;
3914
+ if (thoughtTokens > 0) stats.push(`~${formatTokens(thoughtTokens)} thought`);
3915
+ const status = stats.length > 0 ? ` (${stats.join(" \xB7 ")})` : "";
3831
3916
  return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
3832
3917
  /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3833
3918
  spinner,
3834
3919
  " ",
3835
3920
  label,
3836
- elapsed
3921
+ status
3837
3922
  ] }),
3838
3923
  tail !== "" && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3839
3924
  " ",
@@ -3990,6 +4075,12 @@ var PromptQueue = class {
3990
4075
  this.items.push({ id: this.nextId++, ...prompt });
3991
4076
  return this.items.length;
3992
4077
  }
4078
+ // Jump the queue: steering prompts the running turn never delivered are
4079
+ // recovered here when the turn ends, and they were submitted before the
4080
+ // already-queued entries.
4081
+ enqueueFront(prompt) {
4082
+ this.items.unshift({ id: this.nextId++, ...prompt });
4083
+ }
3993
4084
  dequeue() {
3994
4085
  return this.items.shift();
3995
4086
  }
@@ -4035,9 +4126,56 @@ async function executeShellBang(raw, cwd, signal) {
4035
4126
  };
4036
4127
  }
4037
4128
 
4129
+ // src/cli/terminal-title.ts
4130
+ import path14 from "path";
4131
+ var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4132
+ var TITLE_INTERVAL_MS = 120;
4133
+ var CONTROL_CHARS = /[\x00-\x1f\x7f]/g;
4134
+ function setTerminalTitle(title, deps = {}) {
4135
+ const isTTY = deps.isTTY ?? Boolean(process.stdout.isTTY);
4136
+ if (!isTTY) return;
4137
+ const write = deps.write ?? ((text) => process.stdout.write(text));
4138
+ write(`\x1B]0;${title.replace(CONTROL_CHARS, "")}\x07`);
4139
+ }
4140
+ var TerminalTitle = class _TerminalTitle {
4141
+ constructor(base, deps) {
4142
+ this.base = base;
4143
+ this.deps = deps;
4144
+ }
4145
+ base;
4146
+ deps;
4147
+ timer = null;
4148
+ frame = 0;
4149
+ static forCwd(cwd, deps = {}) {
4150
+ return new _TerminalTitle(`star-cli \u2014 ${path14.basename(cwd) || cwd}`, deps);
4151
+ }
4152
+ start() {
4153
+ if (this.timer) return;
4154
+ this.render(SPINNER_FRAMES2[this.frame] ?? "\u280B");
4155
+ this.timer = setInterval(() => {
4156
+ this.frame = (this.frame + 1) % SPINNER_FRAMES2.length;
4157
+ this.render(SPINNER_FRAMES2[this.frame] ?? "\u280B");
4158
+ }, TITLE_INTERVAL_MS);
4159
+ this.timer.unref();
4160
+ }
4161
+ // "done" leaves a bell glyph in the title until the next turn starts;
4162
+ // "idle" (interrupt, unmount) restores the bare title.
4163
+ stop(kind = "done") {
4164
+ if (this.timer) {
4165
+ clearInterval(this.timer);
4166
+ this.timer = null;
4167
+ }
4168
+ this.frame = 0;
4169
+ this.render(kind === "done" ? resolveIconMode(this.deps.env) === "emoji" ? "\u{1F514}" : "!" : "");
4170
+ }
4171
+ render(prefix) {
4172
+ setTerminalTitle(prefix ? `${prefix} ${this.base}` : this.base, this.deps);
4173
+ }
4174
+ };
4175
+
4038
4176
  // src/cli/update-check.ts
4039
4177
  import { mkdir, readFile as readFile4, rename, writeFile } from "fs/promises";
4040
- import path14 from "path";
4178
+ import path15 from "path";
4041
4179
  var REGISTRY_URL2 = "https://registry.npmjs.org/@cryer%2fstar-cli/latest";
4042
4180
  var TIMEOUT_MS = 5e3;
4043
4181
  var THROTTLE_MS = 24 * 60 * 60 * 1e3;
@@ -4045,7 +4183,7 @@ async function defaultFetcher2(url) {
4045
4183
  return fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
4046
4184
  }
4047
4185
  function defaultStatePath() {
4048
- return path14.join(starHome(), "update-check.json");
4186
+ return path15.join(starHome(), "update-check.json");
4049
4187
  }
4050
4188
  async function readState(statePath) {
4051
4189
  try {
@@ -4060,7 +4198,7 @@ async function readState(statePath) {
4060
4198
  }
4061
4199
  async function writeState(statePath, state) {
4062
4200
  try {
4063
- await mkdir(path14.dirname(statePath), { recursive: true });
4201
+ await mkdir(path15.dirname(statePath), { recursive: true });
4064
4202
  const tmp = `${statePath}.tmp-${process.pid}`;
4065
4203
  await writeFile(tmp, JSON.stringify(state), "utf8");
4066
4204
  await rename(tmp, statePath);
@@ -4131,7 +4269,7 @@ var BUSY_BLOCKED_COMMANDS = /* @__PURE__ */ new Set([
4131
4269
  function formatUsage(usage) {
4132
4270
  return `API usage this session: ${usage.requests} requests, ${formatTokens(usage.promptTokens)} prompt + ${formatTokens(usage.completionTokens)} completion = ${formatTokens(usage.totalTokens)} tokens`;
4133
4271
  }
4134
- var SESSION_PICKER_CAP = 20;
4272
+ var SESSION_PICKER_CAP = 100;
4135
4273
  var SESSION_PREVIEW_MAX = 60;
4136
4274
  var MAX_VISIBLE_QUEUED = 5;
4137
4275
  var REASONING_EFFORT_LEVELS = ["minimal", "low", "medium", "high", "max"];
@@ -4186,6 +4324,7 @@ function Repl({
4186
4324
  const planApprovalRef = useRef6(false);
4187
4325
  const pendingRewindRef = useRef6(null);
4188
4326
  const pickerRef = useRef6(null);
4327
+ const titleRef = useRef6(null);
4189
4328
  const pendingConnectRef = useRef6(null);
4190
4329
  const modelNameRef = useRef6(model);
4191
4330
  const sessionStoreRef = useRef6(sessionStore);
@@ -4213,11 +4352,11 @@ function Repl({
4213
4352
  null
4214
4353
  );
4215
4354
  const pushMessage = useCallback(
4216
- (role, text, note, tight, interrupted, diff) => {
4355
+ (role, text, note, tight, interrupted, diff, steer) => {
4217
4356
  setMessages((prev) => {
4218
4357
  const next = [
4219
4358
  ...prev,
4220
- { id: nextIdRef.current++, role, text, note, tight, interrupted, diff }
4359
+ { id: nextIdRef.current++, role, text, note, tight, interrupted, diff, steer }
4221
4360
  ];
4222
4361
  messagesRef.current = next;
4223
4362
  return next;
@@ -4268,10 +4407,10 @@ function Repl({
4268
4407
  setPendingConnectState(p);
4269
4408
  }, []);
4270
4409
  const showPicker = useCallback(
4271
- (title, options) => {
4410
+ (title, options, onDelete) => {
4272
4411
  if (pickerRef.current) return Promise.resolve(null);
4273
4412
  return new Promise((resolve) => {
4274
- setPicker({ title, options, resolve });
4413
+ setPicker({ title, options, resolve, onDelete });
4275
4414
  });
4276
4415
  },
4277
4416
  [setPicker]
@@ -4398,6 +4537,7 @@ function Repl({
4398
4537
  tickerStopRef.current?.();
4399
4538
  abortRef.current?.abort();
4400
4539
  pendingRef.current?.resolve(false);
4540
+ titleRef.current?.stop("idle");
4401
4541
  };
4402
4542
  }, [attachConfirmHandler]);
4403
4543
  useEffect4(() => {
@@ -4664,7 +4804,7 @@ function Repl({
4664
4804
  setTodoSession(store.id);
4665
4805
  setTodos(await loadTodos(cwd, store.id));
4666
4806
  hydrateSnapshots(await loadSessionSnapshots(store.dir));
4667
- const display = buildDisplayMessages(toCoreMessages(resumed.messages));
4807
+ const display = buildDisplayMessages(resumed.messages);
4668
4808
  nextIdRef.current = display.length;
4669
4809
  applyMessages(display);
4670
4810
  usageRef.current = resumed.meta.usage ? { ...resumed.meta.usage } : emptyUsage();
@@ -4674,6 +4814,10 @@ function Repl({
4674
4814
  },
4675
4815
  [applyMessages, cwd, stopBackgroundWork]
4676
4816
  );
4817
+ const terminalTitle = useCallback(() => {
4818
+ titleRef.current ??= TerminalTitle.forCwd(cwd);
4819
+ return titleRef.current;
4820
+ }, [cwd]);
4677
4821
  const runStream = useCallback(
4678
4822
  async (input, extraImages = []) => {
4679
4823
  const resolved = await resolveMentions(input, cwd);
@@ -4700,6 +4844,7 @@ function Repl({
4700
4844
  setThoughtSummary(null);
4701
4845
  setStreamingText("");
4702
4846
  setIsStreaming(true);
4847
+ terminalTitle().start();
4703
4848
  flushedRef.current = { streamed: "", reasoning: "" };
4704
4849
  const commitStreamed = (tight) => {
4705
4850
  const uncommitted = streamedRef.current.slice(lastCommittedLenRef.current);
@@ -4859,6 +5004,7 @@ function Repl({
4859
5004
  lastCommittedLenRef.current = 0;
4860
5005
  setStreamingText(null);
4861
5006
  const interrupted = controller.signal.aborted;
5007
+ terminalTitle().stop(interrupted ? "idle" : "done");
4862
5008
  notifyBell({
4863
5009
  enabled: config.notifyBell,
4864
5010
  thresholdSec: config.notifyBellThresholdSec,
@@ -4901,6 +5047,14 @@ function Repl({
4901
5047
  `Session budget exceeded: $${formatDollars(cost ?? 0)} of $${formatDollars(budget ?? 0)} (sessionBudgetUsd). New prompts are blocked \u2014 raise sessionBudgetUsd in the config or start a /new session.`
4902
5048
  );
4903
5049
  }
5050
+ const backend2 = backendRef.current;
5051
+ if (backend2 instanceof AgentLoop) {
5052
+ const undelivered = backend2.takeUndeliveredSteers();
5053
+ for (const text of [...undelivered].reverse()) {
5054
+ queueRef.current.enqueueFront({ text, images: [] });
5055
+ }
5056
+ if (undelivered.length > 0) setQueueItems(queueRef.current.list());
5057
+ }
4904
5058
  const nextPrompt = queueRef.current.dequeue();
4905
5059
  if (nextPrompt) {
4906
5060
  if (status === "exceeded") {
@@ -4924,7 +5078,8 @@ function Repl({
4924
5078
  setPlanApproval,
4925
5079
  cwd,
4926
5080
  config,
4927
- sessionCostUsd
5081
+ sessionCostUsd,
5082
+ terminalTitle
4928
5083
  ]
4929
5084
  );
4930
5085
  useEffect4(() => {
@@ -5072,11 +5227,41 @@ ${lines.join("\n")}`;
5072
5227
  };
5073
5228
  })
5074
5229
  );
5230
+ const deleted = [];
5231
+ const onDelete = async (id) => {
5232
+ const picker2 = pickerRef.current;
5233
+ const option = (picker2?.options ?? options).find((o) => o.value === id);
5234
+ try {
5235
+ await deleteSession(id);
5236
+ } catch (error) {
5237
+ pushMessage(
5238
+ "system",
5239
+ `Failed to delete session ${shortSessionId(id)}: ${error instanceof Error ? error.message : String(error)}`
5240
+ );
5241
+ return picker2?.options ?? options;
5242
+ }
5243
+ deleted.push(option?.label ?? shortSessionId(id));
5244
+ const remaining = (picker2?.options ?? options).filter((o) => o.value !== id);
5245
+ if (picker2) {
5246
+ if (remaining.length === 0) {
5247
+ handlePickerCancel();
5248
+ } else {
5249
+ setPicker({ ...picker2, options: remaining });
5250
+ }
5251
+ }
5252
+ return remaining;
5253
+ };
5075
5254
  const picked = await showPicker(
5076
5255
  all ? "Resume a session (all directories)" : "Resume a session",
5077
- options
5256
+ options,
5257
+ onDelete
5078
5258
  );
5079
- if (!picked) return "Session unchanged \u2014 picker cancelled.";
5259
+ if (!picked) {
5260
+ if (deleted.length > 0) {
5261
+ return `Deleted ${deleted.length} session(s): ${deleted.join(", ")}.`;
5262
+ }
5263
+ return "Session unchanged \u2014 picker cancelled.";
5264
+ }
5080
5265
  return resume(picked);
5081
5266
  },
5082
5267
  forkSession: async () => {
@@ -5293,7 +5478,7 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5293
5478
  if (result.messageIndex !== null && current instanceof AgentLoop) {
5294
5479
  retracted = await current.retractFromIndex(result.messageIndex);
5295
5480
  if (retracted > 0) {
5296
- const display = buildDisplayMessages([...current.getMessages()]);
5481
+ const display = buildDisplayMessages([...current.getStarMessages()]);
5297
5482
  nextIdRef.current = display.length;
5298
5483
  redrawMessages(display);
5299
5484
  }
@@ -5390,6 +5575,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5390
5575
  setPendingRewind,
5391
5576
  setPendingConnect,
5392
5577
  showPicker,
5578
+ handlePickerCancel,
5579
+ setPicker,
5393
5580
  stopBackgroundWork
5394
5581
  ]);
5395
5582
  const runShellBang = useCallback(
@@ -5479,13 +5666,55 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5479
5666
  const images = pendingImagesRef.current.map((staged) => staged.image);
5480
5667
  if (images.length > 0) clearPendingImages();
5481
5668
  if (abortRef.current || busyCommandRef.current) {
5669
+ const backend2 = backendRef.current;
5670
+ if (abortRef.current && images.length === 0 && backend2 instanceof AgentLoop) {
5671
+ void (async () => {
5672
+ const picked = await showPicker("Turn is running \u2014 how should this be sent?", [
5673
+ {
5674
+ value: "steer",
5675
+ label: "insert into the running turn",
5676
+ description: "delivered to the model at the next step boundary"
5677
+ },
5678
+ {
5679
+ value: "queue",
5680
+ label: "queue for after the turn",
5681
+ description: "sent as a new prompt once the current turn finishes"
5682
+ }
5683
+ ]);
5684
+ if (picked === "steer") {
5685
+ backend2.injectUserMessage(text);
5686
+ pushMessage(
5687
+ "user",
5688
+ text,
5689
+ "steering the running turn \u2014 the model sees it at the next step",
5690
+ void 0,
5691
+ void 0,
5692
+ void 0,
5693
+ true
5694
+ );
5695
+ return;
5696
+ }
5697
+ queueRef.current.enqueue({ text, images });
5698
+ setQueueItems(queueRef.current.list());
5699
+ })();
5700
+ return;
5701
+ }
5482
5702
  queueRef.current.enqueue({ text, images });
5483
5703
  setQueueItems(queueRef.current.list());
5484
5704
  return;
5485
5705
  }
5486
5706
  void runStream(text, images);
5487
5707
  },
5488
- [registry, pushMessage, runStream, runShellBang, config, clearPendingImages, sessionCostUsd]
5708
+ [
5709
+ registry,
5710
+ pushMessage,
5711
+ runStream,
5712
+ runShellBang,
5713
+ config,
5714
+ clearPendingImages,
5715
+ sessionCostUsd,
5716
+ showPicker
5717
+ ]
5489
5718
  );
5490
5719
  const commandHints = useMemo(
5491
5720
  () => registry.list().map((cmd) => ({
@@ -5497,15 +5726,21 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5497
5726
  [registry]
5498
5727
  );
5499
5728
  useEffect4(() => {
5500
- setGitBranch(getGitSummaryCached(cwd)?.branch ?? null);
5501
- const current = backendRef.current;
5502
- if (current instanceof AgentLoop) {
5503
- const window_ = contextWindowTokens(config, modelNameRef.current);
5504
- const pct = (estimateTokens(current.getMessages()) + current.getToolSchemaTokens()) / window_ * 100;
5505
- setContextPercent(pct < 10 ? Math.round(pct * 10) / 10 : Math.round(pct));
5506
- } else {
5507
- setContextPercent(null);
5508
- }
5729
+ const refresh = () => {
5730
+ setGitBranch(getGitSummaryCached(cwd)?.branch ?? null);
5731
+ const current = backendRef.current;
5732
+ if (current instanceof AgentLoop) {
5733
+ const window_ = contextWindowTokens(config, modelNameRef.current);
5734
+ const pct = current.estimateContextTokens() / window_ * 100;
5735
+ setContextPercent(pct < 10 ? Math.round(pct * 10) / 10 : Math.round(pct));
5736
+ } else {
5737
+ setContextPercent(null);
5738
+ }
5739
+ };
5740
+ refresh();
5741
+ if (!isStreaming) return;
5742
+ const timer = setInterval(refresh, 2e3);
5743
+ return () => clearInterval(timer);
5509
5744
  }, [cwd, config, isStreaming, epoch, modelName]);
5510
5745
  const sessionCost = sessionCostUsd();
5511
5746
  return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
@@ -5545,7 +5780,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5545
5780
  title: picker.title,
5546
5781
  options: picker.options,
5547
5782
  onSelect: handlePickerSelect,
5548
- onCancel: handlePickerCancel
5783
+ onCancel: handlePickerCancel,
5784
+ onDelete: picker.onDelete
5549
5785
  }
5550
5786
  ),
5551
5787
  pendingConnect && /* @__PURE__ */ jsx11(
@@ -5634,4 +5870,4 @@ export {
5634
5870
  Repl,
5635
5871
  renderRepl
5636
5872
  };
5637
- //# sourceMappingURL=repl-GBVIW5QR.js.map
5873
+ //# sourceMappingURL=repl-YBNTJOSB.js.map