@cryer/star-cli 0.3.8 → 0.3.9

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,8 +6,8 @@ import {
6
6
  buildDisplayMessages,
7
7
  clearSessions,
8
8
  computeCostUsd,
9
- coreMessageText,
10
9
  estimateCost,
10
+ folderIcon,
11
11
  formatByteCount,
12
12
  formatDollars,
13
13
  formatElapsedSeconds,
@@ -25,65 +25,81 @@ import {
25
25
  shortSessionId,
26
26
  splitCommittableLines,
27
27
  summarizeArgs,
28
+ thinkingIcon,
28
29
  toTerminalSafe,
29
- upsertEnvContent
30
- } from "./chunk-IYWYSFSF.js";
30
+ toolIcon
31
+ } from "./chunk-VJFWTXWN.js";
31
32
  import {
32
33
  AgentLoop,
33
- MAX_IMAGE_DIMENSION,
34
- SKIP_DIRS,
35
- TodoStore,
36
34
  WORKING_DIFF_MAX_LINES,
37
35
  appendUserMemory,
38
- bashTool,
39
36
  buildAllowRule,
40
37
  buildCommitPrompt,
41
- clearSnapshots,
42
38
  collectCommitContext,
43
39
  collectWorkingDiff,
44
40
  compactMessages,
41
+ discoverSkills,
42
+ estimateTokens,
43
+ getGitSummaryCached,
44
+ isAllowedByRules,
45
+ isDangerousCommand,
46
+ isGitRepo,
47
+ summarizeMessages
48
+ } from "./chunk-RE3NOULF.js";
49
+ import {
45
50
  contextWindowTokens,
51
+ coreMessageText,
52
+ diffTreeNames,
53
+ reconcileStarMessages,
54
+ toCoreMessages
55
+ } from "./chunk-56ZIA57D.js";
56
+ import {
57
+ createModel,
58
+ reasoningEffortMetadata
59
+ } from "./chunk-MVAMPVTA.js";
60
+ import {
61
+ listModels,
62
+ resolveModelConfig
63
+ } from "./chunk-4IRY44TG.js";
64
+ import {
46
65
  createDefaultRegistry,
66
+ formatTaskFinished,
67
+ formatTaskList,
68
+ formatTaskStarted
69
+ } from "./chunk-OIJYNQFZ.js";
70
+ import {
71
+ MAX_IMAGE_DIMENSION,
72
+ SKIP_DIRS,
73
+ TodoStore,
74
+ bashTool,
75
+ clearSnapshots,
47
76
  createIgnorePredicate,
48
- createModel,
49
77
  defaultAgentTasks,
50
78
  defaultTaskManager,
51
- diffTreeNames,
52
- discoverSkills,
53
79
  downsampleImageIfNeeded,
54
80
  envFilePath,
55
- estimateTokens,
56
81
  formatAgentTaskUpdate,
57
- formatTaskFinished,
58
- formatTaskList,
59
- formatTaskStarted,
60
82
  formatTodos,
61
- getGitSummaryCached,
62
83
  globalConfigPath,
63
84
  hydrateSnapshots,
64
- isAllowedByRules,
65
- isDangerousCommand,
66
- isGitRepo,
67
- listModels,
68
85
  listSnapshots,
69
86
  listTurnSnapshots,
70
87
  loadTodos,
71
88
  parseTodoArgs,
72
- reasoningEffortMetadata,
73
- reconcileToolCalls,
74
89
  resetTodos,
75
- resolveModelConfig,
76
90
  resolveShell,
77
91
  resolveSnapshotContent,
78
92
  rewindToSnapshot,
93
+ sessionsDir,
94
+ setTodoSession,
79
95
  starHome,
80
- summarizeMessages,
96
+ upsertEnvContent,
81
97
  userMemoryPath
82
- } from "./chunk-OHLA2DP7.js";
98
+ } from "./chunk-A5PXPWHB.js";
83
99
 
84
100
  // src/cli/repl.tsx
85
101
  import { Box as Box13, Text as Text14, render, useApp } from "ink";
86
- import { useCallback, useEffect as useEffect3, useMemo, useRef as useRef6, useState as useState5 } from "react";
102
+ import { useCallback, useEffect as useEffect4, useMemo, useRef as useRef6, useState as useState7 } from "react";
87
103
 
88
104
  // src/config/save.ts
89
105
  import fs from "fs";
@@ -99,6 +115,10 @@ async function writeFileAtomic(filePath, content) {
99
115
  const tmpPath = `${filePath}.${process.pid}.tmp`;
100
116
  await fs.promises.writeFile(tmpPath, content, "utf8");
101
117
  await fs.promises.rename(tmpPath, filePath);
118
+ try {
119
+ await fs.promises.chmod(filePath, 384);
120
+ } catch {
121
+ }
102
122
  }
103
123
  function upsertPermissionRuleText(content, kind, rules, hadPermissionsTable) {
104
124
  const newLine = `${kind} = [${rules.map(tomlString).join(", ")}]`;
@@ -265,8 +285,8 @@ async function saveReasoningEffort(modelName, effort) {
265
285
  async function resumeSession(id) {
266
286
  const store = await SessionStore.open(id);
267
287
  if (!store) return null;
268
- const [meta, loaded] = await Promise.all([store.meta(), store.messages()]);
269
- const messages = reconcileToolCalls(loaded);
288
+ const [meta, loaded] = await Promise.all([store.meta(), store.starMessages()]);
289
+ const messages = reconcileStarMessages(loaded);
270
290
  if (messages.length !== loaded.length) {
271
291
  await store.replaceMessages(messages);
272
292
  }
@@ -609,14 +629,14 @@ async function compactSession(opts) {
609
629
  if (!(backend instanceof AgentLoop)) {
610
630
  return { message: "Current backend does not support compaction.", compacted: false };
611
631
  }
612
- const messages = [...backend.getMessages()];
632
+ const messages = [...backend.getStarMessages()];
613
633
  if (messages.length < MIN_COMPACT_MESSAGES) {
614
634
  return {
615
635
  message: `Nothing to compact: history has only ${messages.length} message(s).`,
616
636
  compacted: false
617
637
  };
618
638
  }
619
- const beforeTokens = estimateTokens(messages);
639
+ const beforeTokens = estimateTokens(toCoreMessages(messages));
620
640
  const compacted = compactMessages(messages, config.contextMaxTokens, { force: true });
621
641
  if (!compacted.compacted) {
622
642
  return {
@@ -629,33 +649,44 @@ async function compactSession(opts) {
629
649
  compacted,
630
650
  config,
631
651
  backend.getAuxModel(),
632
- backend.getAuxTemperature()
652
+ backend.getAuxTemperature(),
653
+ opts.signal
633
654
  );
655
+ if (next === null || opts.signal?.aborted) {
656
+ return { message: "Compaction cancelled \u2014 history left unchanged.", compacted: false };
657
+ }
634
658
  await backend.loadMessages(next);
635
659
  await sessionStore?.replaceMessages(next);
636
- const afterTokens = estimateTokens(next);
660
+ const coreNext = toCoreMessages(next);
661
+ const afterTokens = estimateTokens(coreNext);
637
662
  return {
638
663
  message: `Compacted context: ${messages.length} -> ${next.length} messages (~${formatTokens(beforeTokens)} -> ~${formatTokens(afterTokens)} estimated tokens).`,
639
664
  compacted: true,
640
- messages: next
665
+ messages: coreNext
641
666
  };
642
667
  }
643
- async function applyCompactionSummary(original, compacted, config, model, temperature) {
668
+ async function applyCompactionSummary(original, compacted, config, model, temperature, signal) {
644
669
  if (config.contextCompaction !== "summary") {
645
670
  return compacted.messages;
646
671
  }
647
- const headCount = compacted.messages[0]?.role === "system" ? 1 : 0;
648
- const dropped = original.slice(headCount, headCount + compacted.droppedCount);
672
+ const headCount = compacted.messages[0]?.message.role === "system" ? 1 : 0;
673
+ const dropped = toCoreMessages(original.slice(headCount, headCount + compacted.droppedCount));
649
674
  try {
650
- const summary = await summarizeMessages(dropped, model, void 0, temperature);
651
- const messages = compacted.messages.slice();
652
- messages[headCount] = {
653
- role: "user",
654
- content: `[earlier conversation summarized]
675
+ const summary = await summarizeMessages(dropped, model, signal, temperature);
676
+ if (summary.trim().length > 0) {
677
+ const messages = compacted.messages.slice();
678
+ messages[headCount] = {
679
+ message: {
680
+ role: "user",
681
+ content: `[earlier conversation summarized]
655
682
  ${summary}`
656
- };
657
- return messages;
683
+ }
684
+ };
685
+ return messages;
686
+ }
687
+ return compacted.messages;
658
688
  } catch {
689
+ if (signal?.aborted) return null;
659
690
  return compacted.messages;
660
691
  }
661
692
  }
@@ -741,7 +772,11 @@ function stringifyPart(value) {
741
772
  import { readFileSync } from "fs";
742
773
 
743
774
  // src/session/search.ts
775
+ import fs3 from "fs/promises";
776
+ import path4 from "path";
777
+ import { StringDecoder } from "string_decoder";
744
778
  var SNIPPET_LENGTH = 100;
779
+ var SEARCH_READ_CHUNK_BYTES = 64 * 1024;
745
780
  function flattenMessage(message) {
746
781
  const content = message.content;
747
782
  if (typeof content === "string") return content;
@@ -768,17 +803,113 @@ function makeSnippet(text, query) {
768
803
  start = Math.max(0, end - SNIPPET_LENGTH);
769
804
  return `${start > 0 ? "\u2026" : ""}${flat.slice(start, end)}${end < flat.length ? "\u2026" : ""}`;
770
805
  }
806
+ async function streamSessionPieces(id, onPiece) {
807
+ let handle = null;
808
+ try {
809
+ handle = await fs3.open(path4.join(sessionsDir(), id, "messages.jsonl"), "r");
810
+ const decoder = new StringDecoder("utf8");
811
+ const buffer = Buffer.alloc(SEARCH_READ_CHUNK_BYTES);
812
+ let tail = "";
813
+ let stop = false;
814
+ const pushLine = (line) => {
815
+ if (stop) return;
816
+ const trimmed = line.trim();
817
+ if (!trimmed) return;
818
+ let message;
819
+ try {
820
+ message = JSON.parse(trimmed);
821
+ } catch {
822
+ return;
823
+ }
824
+ const piece = flattenMessage(message);
825
+ if (piece !== "") stop = onPiece(piece);
826
+ };
827
+ while (!stop) {
828
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
829
+ if (bytesRead === 0) break;
830
+ const chunk = tail + decoder.write(buffer.subarray(0, bytesRead));
831
+ const lines = chunk.split("\n");
832
+ tail = lines.pop() ?? "";
833
+ for (const line of lines) pushLine(line);
834
+ }
835
+ if (!stop) pushLine(tail + decoder.end());
836
+ } catch {
837
+ } finally {
838
+ await handle?.close().catch(() => {
839
+ });
840
+ }
841
+ }
842
+ async function sessionTextContains(id, needle) {
843
+ let lowerTail = "";
844
+ let found = false;
845
+ let joined = false;
846
+ await streamSessionPieces(id, (piece) => {
847
+ const segment = joined ? `
848
+ ${piece}` : piece;
849
+ joined = true;
850
+ const haystack = lowerTail + segment.toLowerCase();
851
+ lowerTail = needle.length > 1 ? haystack.slice(1 - needle.length) : "";
852
+ if (haystack.includes(needle)) {
853
+ found = true;
854
+ return true;
855
+ }
856
+ return false;
857
+ });
858
+ return found;
859
+ }
860
+ async function buildSessionSnippet(id, needle, query) {
861
+ const parts = [];
862
+ let lowerTail = "";
863
+ let mode = needle === "" ? "window" : "scan";
864
+ let flatLength = 0;
865
+ let flatLastChar = "";
866
+ let matchEndFlat = 0;
867
+ let stoppedEarly = false;
868
+ const foldCollapsed = (segment) => {
869
+ let folded = (flatLastChar + segment).replace(/\s+/g, " ");
870
+ if (flatLength === 0) {
871
+ if (folded.startsWith(" ")) folded = folded.slice(1);
872
+ } else {
873
+ folded = folded.slice(1);
874
+ }
875
+ flatLength += folded.length;
876
+ if (folded.length > 0) flatLastChar = folded.at(-1) ?? "";
877
+ };
878
+ await streamSessionPieces(id, (piece) => {
879
+ const segment = parts.length === 0 ? piece : `
880
+ ${piece}`;
881
+ parts.push(piece);
882
+ if (mode === "scan") {
883
+ const haystack = lowerTail + segment.toLowerCase();
884
+ lowerTail = needle.length > 1 ? haystack.slice(1 - needle.length) : "";
885
+ if (!haystack.includes(needle)) return false;
886
+ const collapsed = parts.join("\n").replace(/\s+/g, " ").replace(/^ /, "");
887
+ const index = collapsed.toLowerCase().indexOf(needle);
888
+ if (index === -1) {
889
+ mode = "head";
890
+ return false;
891
+ }
892
+ mode = "window";
893
+ matchEndFlat = index + needle.length;
894
+ flatLength = collapsed.length;
895
+ flatLastChar = collapsed.at(-1) ?? "";
896
+ } else if (mode === "window") {
897
+ foldCollapsed(segment);
898
+ }
899
+ const stop = mode === "window" && flatLength > matchEndFlat + SNIPPET_LENGTH;
900
+ if (stop) stoppedEarly = true;
901
+ return stop;
902
+ });
903
+ const text = parts.join("\n");
904
+ return makeSnippet(stoppedEarly ? `${text}\0` : text, query);
905
+ }
771
906
  async function searchSessions(query, limit = 10) {
772
907
  const hits = [];
773
908
  const needle = query.toLowerCase();
774
909
  for (const meta of await SessionStore.list()) {
775
910
  if (hits.length >= limit) break;
776
- const store = await SessionStore.open(meta.id);
777
- if (!store) continue;
778
- const text = (await store.messages()).map(flattenMessage).filter((part) => part.length > 0).join("\n");
779
- if (text.toLowerCase().includes(needle)) {
780
- hits.push({ meta, snippet: makeSnippet(text, query) });
781
- }
911
+ if (needle !== "" && !await sessionTextContains(meta.id, needle)) continue;
912
+ hits.push({ meta, snippet: await buildSessionSnippet(meta.id, needle, query) });
782
913
  }
783
914
  return hits;
784
915
  }
@@ -794,7 +925,7 @@ function formatSearchResults(hits, query) {
794
925
 
795
926
  // src/cli/diff-preview.ts
796
927
  import { readFile as readFile2 } from "fs/promises";
797
- import path4 from "path";
928
+ import path5 from "path";
798
929
  var CONTEXT_LINES = 2;
799
930
  var MAX_HUNK_LINES = 15;
800
931
  var MAX_TOTAL_LINES = 12;
@@ -955,7 +1086,7 @@ async function generateDiffPreview(toolName, args, cwd) {
955
1086
  if (toolName !== "edit_file" && toolName !== "write_file") return null;
956
1087
  const filePath = getStringArg(args, "path");
957
1088
  if (filePath === void 0) return null;
958
- const absolute = path4.resolve(cwd, filePath);
1089
+ const absolute = path5.resolve(cwd, filePath);
959
1090
  let existing;
960
1091
  try {
961
1092
  existing = await readFile2(absolute, "utf8");
@@ -1027,6 +1158,7 @@ ${sections.join("\n\n")}`);
1027
1158
  description: "Delete stored sessions: this directory by default, every session with --all (the current session is kept)",
1028
1159
  usage: "/clear-sessions [--all]",
1029
1160
  category: "Sessions",
1161
+ argHints: ["--all"],
1030
1162
  async run(args, ctx) {
1031
1163
  const arg = args.trim();
1032
1164
  if (arg !== "" && arg !== "--all") {
@@ -1180,6 +1312,7 @@ ${sections.join("\n\n")}`);
1180
1312
  description: "Set the permission mode (opens a picker when no mode is given)",
1181
1313
  usage: "/permission [ask|auto|readonly|yolo]",
1182
1314
  category: "Settings",
1315
+ argHints: ["ask", "auto", "readonly", "yolo", "plan"],
1183
1316
  async run(args, ctx) {
1184
1317
  const arg = args.trim();
1185
1318
  if (!arg) {
@@ -1203,6 +1336,7 @@ ${sections.join("\n\n")}`);
1203
1336
  description: "Show user memory (MEMORY.md), or add an entry",
1204
1337
  usage: "/memory [add <text>]",
1205
1338
  category: "Settings",
1339
+ argHints: ["add"],
1206
1340
  run(args, ctx) {
1207
1341
  const arg = args.trim();
1208
1342
  if (arg === "add" || arg.startsWith("add ")) {
@@ -1282,6 +1416,7 @@ ${content}`);
1282
1416
  description: "Generate an AGENTS.md for the current project",
1283
1417
  usage: "/init [force]",
1284
1418
  category: "Settings",
1419
+ argHints: ["force"],
1285
1420
  async run(args, ctx) {
1286
1421
  ctx.addSystemMessage(await ctx.initProject(args));
1287
1422
  }
@@ -1341,6 +1476,7 @@ ${lines.join("\n")}`);
1341
1476
  description: "Copy the last assistant reply to the clipboard (`all` for the whole conversation)",
1342
1477
  usage: "/copy [all]",
1343
1478
  category: "Sessions",
1479
+ argHints: ["all"],
1344
1480
  async run(args, ctx) {
1345
1481
  const arg = args.trim();
1346
1482
  if (arg !== "" && arg !== "all") {
@@ -1406,8 +1542,8 @@ ${result.diff}${note ? `
1406
1542
 
1407
1543
  // src/cli/commands/connect.ts
1408
1544
  import { spawn } from "child_process";
1409
- import fs3 from "fs";
1410
- import path5 from "path";
1545
+ import fs4 from "fs";
1546
+ import path6 from "path";
1411
1547
  var PROVIDER_PRESETS = [
1412
1548
  {
1413
1549
  key: "openai",
@@ -1547,14 +1683,14 @@ function saveConnection(answers, configPath = globalConfigPath(), envPath = envF
1547
1683
  promptPrice: 0,
1548
1684
  completionPrice: 0
1549
1685
  };
1550
- fs3.mkdirSync(path5.dirname(envPath), { recursive: true });
1686
+ fs4.mkdirSync(path6.dirname(envPath), { recursive: true });
1551
1687
  let envContent = "";
1552
1688
  try {
1553
- envContent = fs3.readFileSync(envPath, "utf8");
1689
+ envContent = fs4.readFileSync(envPath, "utf8");
1554
1690
  } catch {
1555
1691
  envContent = "";
1556
1692
  }
1557
- fs3.writeFileSync(
1693
+ fs4.writeFileSync(
1558
1694
  envPath,
1559
1695
  upsertEnvContent(envContent, provider.apiKeyEnv ?? "", answers.apiKey),
1560
1696
  {
@@ -1563,21 +1699,21 @@ function saveConnection(answers, configPath = globalConfigPath(), envPath = envF
1563
1699
  }
1564
1700
  );
1565
1701
  try {
1566
- fs3.chmodSync(envPath, 384);
1702
+ fs4.chmodSync(envPath, 384);
1567
1703
  } catch {
1568
1704
  }
1569
1705
  if (provider.apiKeyEnv) {
1570
1706
  process.env[provider.apiKeyEnv] = answers.apiKey;
1571
1707
  }
1572
- fs3.mkdirSync(path5.dirname(configPath), { recursive: true });
1708
+ fs4.mkdirSync(path6.dirname(configPath), { recursive: true });
1573
1709
  let existing = "";
1574
1710
  try {
1575
- existing = fs3.readFileSync(configPath, "utf8");
1711
+ existing = fs4.readFileSync(configPath, "utf8");
1576
1712
  } catch {
1577
1713
  existing = "";
1578
1714
  }
1579
1715
  const separator = existing === "" ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
1580
- fs3.writeFileSync(
1716
+ fs4.writeFileSync(
1581
1717
  configPath,
1582
1718
  existing + separator + buildProviderTomlBlock(provider) + buildModelTomlBlock(model),
1583
1719
  "utf8"
@@ -1602,12 +1738,12 @@ ${content}`;
1602
1738
  function saveDefaultModel(modelName, configPath = globalConfigPath()) {
1603
1739
  let content = "";
1604
1740
  try {
1605
- content = fs3.readFileSync(configPath, "utf8");
1741
+ content = fs4.readFileSync(configPath, "utf8");
1606
1742
  } catch {
1607
1743
  content = "";
1608
1744
  }
1609
- fs3.mkdirSync(path5.dirname(configPath), { recursive: true });
1610
- fs3.writeFileSync(configPath, setDefaultModelInToml(content, modelName), "utf8");
1745
+ fs4.mkdirSync(path6.dirname(configPath), { recursive: true });
1746
+ fs4.writeFileSync(configPath, setDefaultModelInToml(content, modelName), "utf8");
1611
1747
  }
1612
1748
  function openConfigFile(filePath) {
1613
1749
  const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", filePath]] : process.platform === "darwin" ? ["open", [filePath]] : ["xdg-open", [filePath]];
@@ -1622,7 +1758,7 @@ function openConfigFile(filePath) {
1622
1758
 
1623
1759
  // src/cli/commands/custom.ts
1624
1760
  import { readFileSync as readFileSync2, readdirSync } from "fs";
1625
- import path6 from "path";
1761
+ import path7 from "path";
1626
1762
  var NAME_PATTERN = /^[a-z0-9-]+$/;
1627
1763
  var DESCRIPTION_PATTERN = /^<!--\s*description:\s*(.+?)\s*-->\s*$/;
1628
1764
  var DEFAULT_DESCRIPTION = "Custom prompt command";
@@ -1657,14 +1793,14 @@ function scanDir(dir, into) {
1657
1793
  if (!entry.endsWith(".md")) continue;
1658
1794
  const name = entry.slice(0, -".md".length);
1659
1795
  if (!NAME_PATTERN.test(name)) continue;
1660
- const parsed = parseCommandFile(path6.join(dir, entry), name);
1796
+ const parsed = parseCommandFile(path7.join(dir, entry), name);
1661
1797
  if (parsed) into.set(name, parsed);
1662
1798
  }
1663
1799
  }
1664
1800
  function loadCustomCommands(cwd, home = starHome()) {
1665
1801
  const byName = /* @__PURE__ */ new Map();
1666
- scanDir(path6.join(home, "commands"), byName);
1667
- scanDir(path6.join(cwd, ".star", "commands"), byName);
1802
+ scanDir(path7.join(home, "commands"), byName);
1803
+ scanDir(path7.join(cwd, ".star", "commands"), byName);
1668
1804
  return [...byName.values()];
1669
1805
  }
1670
1806
  function registerCustomCommands(registry, cwd, home) {
@@ -1690,8 +1826,8 @@ function registerCustomCommands(registry, cwd, home) {
1690
1826
  }
1691
1827
 
1692
1828
  // src/cli/commands/doctor.ts
1693
- import fs4 from "fs/promises";
1694
- import path7 from "path";
1829
+ import fs5 from "fs/promises";
1830
+ import path8 from "path";
1695
1831
  var MIN_NODE_MAJOR = 20;
1696
1832
  var REGISTRY_URL = "https://registry.npmjs.org/";
1697
1833
  var REGISTRY_TIMEOUT_MS = 5e3;
@@ -1705,7 +1841,7 @@ function nodeMajor(version) {
1705
1841
  }
1706
1842
  async function pathExists(target) {
1707
1843
  try {
1708
- await fs4.access(target);
1844
+ await fs5.access(target);
1709
1845
  return true;
1710
1846
  } catch {
1711
1847
  return false;
@@ -1743,7 +1879,7 @@ async function runDoctor(opts) {
1743
1879
  detail: `${spec.label} (${spec.shell})`
1744
1880
  });
1745
1881
  }
1746
- const configPath = path7.join(home, "config.toml");
1882
+ const configPath = path8.join(home, "config.toml");
1747
1883
  const configExists = await pathExists(configPath);
1748
1884
  checks.push({
1749
1885
  name: "STAR_HOME / config",
@@ -1780,14 +1916,14 @@ async function runDoctor(opts) {
1780
1916
  });
1781
1917
  }
1782
1918
  }
1783
- const sessions = path7.join(home, "sessions");
1919
+ const sessions = path8.join(home, "sessions");
1784
1920
  let writable = false;
1785
1921
  let writeError = "";
1786
1922
  try {
1787
- await fs4.mkdir(sessions, { recursive: true });
1788
- const probe = path7.join(sessions, `.doctor-probe-${process.pid}`);
1789
- await fs4.writeFile(probe, "ok");
1790
- await fs4.unlink(probe);
1923
+ await fs5.mkdir(sessions, { recursive: true });
1924
+ const probe = path8.join(sessions, `.doctor-probe-${process.pid}`);
1925
+ await fs5.writeFile(probe, "ok");
1926
+ await fs5.unlink(probe);
1791
1927
  writable = true;
1792
1928
  } catch (error) {
1793
1929
  writeError = error instanceof Error ? error.message : String(error);
@@ -1822,15 +1958,15 @@ function formatDoctorReport(report) {
1822
1958
  }
1823
1959
 
1824
1960
  // src/cli/commands/init-project.ts
1825
- import fs5 from "fs/promises";
1826
- import path8 from "path";
1961
+ import fs6 from "fs/promises";
1962
+ import path9 from "path";
1827
1963
  import { generateText } from "ai";
1828
1964
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", "coverage"]);
1829
1965
  var MAX_ENTRIES = 20;
1830
1966
  var README_HEAD_LINES = 5;
1831
1967
  async function fileExists2(target) {
1832
1968
  try {
1833
- await fs5.access(target);
1969
+ await fs6.access(target);
1834
1970
  return true;
1835
1971
  } catch {
1836
1972
  return false;
@@ -1838,7 +1974,7 @@ async function fileExists2(target) {
1838
1974
  }
1839
1975
  async function readPackageJson(cwd) {
1840
1976
  try {
1841
- const raw = await fs5.readFile(path8.join(cwd, "package.json"), "utf8");
1977
+ const raw = await fs6.readFile(path9.join(cwd, "package.json"), "utf8");
1842
1978
  const parsed = JSON.parse(raw);
1843
1979
  if (typeof parsed !== "object" || parsed === null) return null;
1844
1980
  return parsed;
@@ -1855,10 +1991,10 @@ function stringRecord(value) {
1855
1991
  return out;
1856
1992
  }
1857
1993
  async function detectPackageManager(cwd) {
1858
- if (await fileExists2(path8.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1859
- if (await fileExists2(path8.join(cwd, "yarn.lock"))) return "yarn";
1860
- if (await fileExists2(path8.join(cwd, "package-lock.json"))) return "npm";
1861
- if (await fileExists2(path8.join(cwd, "bun.lockb"))) return "bun";
1994
+ if (await fileExists2(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1995
+ if (await fileExists2(path9.join(cwd, "yarn.lock"))) return "yarn";
1996
+ if (await fileExists2(path9.join(cwd, "package-lock.json"))) return "npm";
1997
+ if (await fileExists2(path9.join(cwd, "bun.lockb"))) return "bun";
1862
1998
  return null;
1863
1999
  }
1864
2000
  function inferCommands(pm, scripts) {
@@ -1873,7 +2009,7 @@ function inferCommands(pm, scripts) {
1873
2009
  }
1874
2010
  async function listEntries(cwd) {
1875
2011
  try {
1876
- const dirents = await fs5.readdir(cwd, { withFileTypes: true });
2012
+ const dirents = await fs6.readdir(cwd, { withFileTypes: true });
1877
2013
  const entries = [];
1878
2014
  for (const dirent of dirents) {
1879
2015
  if (dirent.isDirectory()) {
@@ -1892,7 +2028,7 @@ async function listEntries(cwd) {
1892
2028
  async function readReadmeHead(cwd) {
1893
2029
  for (const name of ["README.md", "readme.md", "README"]) {
1894
2030
  try {
1895
- const raw = await fs5.readFile(path8.join(cwd, name), "utf8");
2031
+ const raw = await fs6.readFile(path9.join(cwd, name), "utf8");
1896
2032
  return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).slice(0, README_HEAD_LINES);
1897
2033
  } catch {
1898
2034
  }
@@ -1905,14 +2041,14 @@ async function scanProject(cwd) {
1905
2041
  const scripts = stringRecord(pkg?.scripts);
1906
2042
  return {
1907
2043
  name: typeof pkg?.name === "string" ? pkg.name : null,
1908
- dirName: path8.basename(cwd),
2044
+ dirName: path9.basename(cwd),
1909
2045
  packageManager,
1910
2046
  scripts,
1911
2047
  dependencies: Object.keys(stringRecord(pkg?.dependencies)),
1912
2048
  devDependencies: Object.keys(stringRecord(pkg?.devDependencies)),
1913
2049
  commands: inferCommands(packageManager, scripts),
1914
- hasTsconfig: await fileExists2(path8.join(cwd, "tsconfig.json")),
1915
- isGitRepo: await fileExists2(path8.join(cwd, ".git")),
2050
+ hasTsconfig: await fileExists2(path9.join(cwd, "tsconfig.json")),
2051
+ isGitRepo: await fileExists2(path9.join(cwd, ".git")),
1916
2052
  readmeHead: await readReadmeHead(cwd),
1917
2053
  entries: await listEntries(cwd)
1918
2054
  };
@@ -1979,7 +2115,7 @@ Write the AGENTS.md.`
1979
2115
  }
1980
2116
  async function initProject(opts) {
1981
2117
  const { cwd, args, model } = opts;
1982
- const target = path8.join(cwd, "AGENTS.md");
2118
+ const target = path9.join(cwd, "AGENTS.md");
1983
2119
  const force = args.split(/\s+/).includes("force");
1984
2120
  if (!force && await fileExists2(target)) {
1985
2121
  return {
@@ -2013,7 +2149,7 @@ async function initProject(opts) {
2013
2149
  content = renderAgentsMd(facts);
2014
2150
  note = " No model available; used the template \u2014 review and refine manually.";
2015
2151
  }
2016
- await fs5.writeFile(target, content);
2152
+ await fs6.writeFile(target, content);
2017
2153
  const lines = content.split("\n").length;
2018
2154
  const how = usedLlm ? "generated with model refinement" : "generated from template";
2019
2155
  return {
@@ -2069,7 +2205,7 @@ function parseSlashCommand(input) {
2069
2205
  }
2070
2206
 
2071
2207
  // src/cli/commands/rewind.ts
2072
- import path9 from "path";
2208
+ import path10 from "path";
2073
2209
  function planRewind(snapshots, id) {
2074
2210
  const target = snapshots.find((snapshot) => snapshot.id === id);
2075
2211
  if (!target) return null;
@@ -2083,7 +2219,7 @@ function planRewind(snapshots, id) {
2083
2219
  return { target, affected, messageIndex };
2084
2220
  }
2085
2221
  function displayPath(filePath, cwd) {
2086
- const relative = path9.relative(cwd, filePath);
2222
+ const relative = path10.relative(cwd, filePath);
2087
2223
  return relative !== "" && !relative.startsWith("..") ? relative : filePath;
2088
2224
  }
2089
2225
  function formatTime(timestamp) {
@@ -2132,12 +2268,27 @@ function didYouMeanSuffix(names) {
2132
2268
  const listed = names.slice(0, 3).map((name) => `/${name}`);
2133
2269
  return ` Did you mean: ${listed.join(", ")}?`;
2134
2270
  }
2271
+ function filterArgHints(input, commands) {
2272
+ if (!input.startsWith("/")) return [];
2273
+ const spaceIndex = input.search(/\s/);
2274
+ if (spaceIndex === -1) return [];
2275
+ const name = input.slice(1, spaceIndex).toLowerCase();
2276
+ const command = commands.find((cmd) => cmd.name.toLowerCase() === name);
2277
+ if (!command?.argHints) return [];
2278
+ const tokenMatch = /\S+$/.exec(input.slice(spaceIndex));
2279
+ const argPrefix = tokenMatch ? tokenMatch[0] : "";
2280
+ const source = command.argHints;
2281
+ const candidates = typeof source === "function" ? source(argPrefix) : source;
2282
+ const prefix = argPrefix.toLowerCase();
2283
+ 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} ` }));
2285
+ }
2135
2286
 
2136
2287
  // src/cli/commands/undo.ts
2137
2288
  import { readFile as readFile3 } from "fs/promises";
2138
- import path10 from "path";
2289
+ import path11 from "path";
2139
2290
  function displayPath2(filePath, cwd) {
2140
- const relative = path10.relative(cwd, filePath);
2291
+ const relative = path11.relative(cwd, filePath);
2141
2292
  return relative !== "" && !relative.startsWith("..") ? relative : filePath;
2142
2293
  }
2143
2294
  async function buildUndoDiffs(snapshots, cwd) {
@@ -2524,11 +2675,11 @@ import { useRef as useRef4, useState as useState3 } from "react";
2524
2675
 
2525
2676
  // src/cli/components/InputBox.tsx
2526
2677
  import { Box as Box2, Text as Text2, useStdout } from "ink";
2527
- import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
2678
+ import { memo, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
2528
2679
 
2529
2680
  // src/cli/path-suggest.ts
2530
2681
  import { readdir } from "fs/promises";
2531
- import path11 from "path";
2682
+ import path12 from "path";
2532
2683
  var MAX_SCAN_ENTRIES = 2e3;
2533
2684
  function extractAtToken(value, cursor) {
2534
2685
  const before = value.slice(0, cursor);
@@ -2543,7 +2694,7 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
2543
2694
  const dirPart = slash >= 0 ? normalized.slice(0, slash + 1) : "";
2544
2695
  const basePrefix = slash >= 0 ? normalized.slice(slash + 1) : normalized;
2545
2696
  if (dirPart.split("/").some((segment) => SKIP_DIRS.has(segment))) return [];
2546
- const dirAbs = path11.resolve(cwd, dirPart === "" ? "." : dirPart);
2697
+ const dirAbs = path12.resolve(cwd, dirPart === "" ? "." : dirPart);
2547
2698
  const entries = await readdir(dirAbs, { withFileTypes: true }).catch(() => null);
2548
2699
  if (!entries) return [];
2549
2700
  const ignore = createIgnorePredicate(cwd);
@@ -2558,7 +2709,7 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
2558
2709
  if (isDir && SKIP_DIRS.has(entry.name)) continue;
2559
2710
  if (entry.name.startsWith(".") && !basePrefix.startsWith(".")) continue;
2560
2711
  if (!entry.name.toLowerCase().startsWith(lower)) continue;
2561
- if (ignore?.(path11.join(dirAbs, entry.name), isDir)) continue;
2712
+ if (ignore?.(path12.join(dirAbs, entry.name), isDir)) continue;
2562
2713
  matches.push({ path: `${dirPart}${entry.name}${isDir ? "/" : ""}`, isDir });
2563
2714
  }
2564
2715
  matches.sort((a, b) => a.path.localeCompare(b.path));
@@ -2656,7 +2807,7 @@ function pasteToken(id, lineCount) {
2656
2807
  function stylePasteTokens(segment) {
2657
2808
  return segment.replace(PASTE_TOKEN_RE, (match) => `\x1B[2m${match}\x1B[22m`);
2658
2809
  }
2659
- function InputBox({
2810
+ var InputBox = memo(function InputBox2({
2660
2811
  isStreaming,
2661
2812
  disabled,
2662
2813
  commands,
@@ -2789,6 +2940,7 @@ function InputBox({
2789
2940
  });
2790
2941
  const suggestionsEnabled = !isStreaming && !disabled && !suggestionsDismissed && !search;
2791
2942
  const suggestions = suggestionsEnabled && commands ? filterCommands(value, commands) : [];
2943
+ const argHints = suggestionsEnabled && commands && cursor === value.length ? filterArgHints(value, commands) : [];
2792
2944
  useEffect2(() => {
2793
2945
  if (!suggestionsEnabled || !cwd) {
2794
2946
  setPathSuggestions([]);
@@ -2807,8 +2959,8 @@ function InputBox({
2807
2959
  cancelled = true;
2808
2960
  };
2809
2961
  }, [value, cursor, cwd, suggestionsEnabled]);
2810
- const showPathSuggestions = suggestions.length === 0 && pathSuggestions.length > 0;
2811
- const suggestionCount = suggestions.length > 0 ? suggestions.length : pathSuggestions.length;
2962
+ const showPathSuggestions = suggestions.length === 0 && argHints.length === 0 && pathSuggestions.length > 0;
2963
+ const suggestionCount = suggestions.length > 0 ? suggestions.length : argHints.length > 0 ? argHints.length : pathSuggestions.length;
2812
2964
  const activeIndex = suggestionCount === 0 ? 0 : Math.min(highlight, suggestionCount - 1);
2813
2965
  const completeHighlighted = () => {
2814
2966
  if (suggestions.length > 0) {
@@ -2818,6 +2970,12 @@ function InputBox({
2818
2970
  edit(text, text.length);
2819
2971
  return;
2820
2972
  }
2973
+ if (argHints.length > 0) {
2974
+ const hint = argHints[activeIndex];
2975
+ if (!hint) return;
2976
+ edit(hint.replacement, hint.replacement.length);
2977
+ return;
2978
+ }
2821
2979
  const sugg = pathSuggestions[activeIndex];
2822
2980
  const token = extractAtToken(value, cursor);
2823
2981
  if (!sugg || !token) return;
@@ -3070,6 +3228,9 @@ ${value.slice(cursor)}`, cursor + 1);
3070
3228
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` - ${cmd.description}` })
3071
3229
  ] }, cmd.name);
3072
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
+ ) }),
3073
3234
  showPathSuggestions && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: pathSuggestions.map(
3074
3235
  (sugg, index) => index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: `@${sugg.path}` }, sugg.path) : /* @__PURE__ */ jsxs2(Text2, { children: [
3075
3236
  /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: `@${sugg.path}` }),
@@ -3077,7 +3238,7 @@ ${value.slice(cursor)}`, cursor + 1);
3077
3238
  ] }, sugg.path)
3078
3239
  ) })
3079
3240
  ] });
3080
- }
3241
+ });
3081
3242
 
3082
3243
  // src/cli/components/TextField.tsx
3083
3244
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -3374,7 +3535,7 @@ function ExitPlanPrompt({ onDecision }) {
3374
3535
 
3375
3536
  // src/cli/components/MessageList.tsx
3376
3537
  import { Box as Box6, Static, Text as Text7, useStdout as useStdout2 } from "ink";
3377
- import { memo } from "react";
3538
+ import { memo as memo2, useState as useState5 } from "react";
3378
3539
 
3379
3540
  // src/cli/components/DiffLines.tsx
3380
3541
  import { Text as Text6 } from "ink";
@@ -3409,10 +3570,28 @@ var roleStyles = {
3409
3570
  system: { label: "\u25CF system", color: "yellow" }
3410
3571
  };
3411
3572
  var interruptedMarker = (text) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: text.length > 0 ? " [interrupted]" : "[interrupted]" });
3412
- var MessageList = memo(function MessageList2({ messages }) {
3573
+ var INITIAL_RENDER_LIMIT = 100;
3574
+ function collapseInitialMessages(messages, limit = INITIAL_RENDER_LIMIT) {
3575
+ if (messages.length <= limit) return { hidden: 0, visible: [...messages] };
3576
+ return { hidden: messages.length - limit, visible: messages.slice(-limit) };
3577
+ }
3578
+ var MessageList = memo2(function MessageList2({ messages }) {
3413
3579
  const { stdout } = useStdout2();
3414
3580
  const termWidth = stdout.columns ?? 80;
3415
- return /* @__PURE__ */ jsx7(Static, { items: messages, children: (message) => {
3581
+ const [initial] = useState5(() => ({
3582
+ length: messages.length,
3583
+ view: collapseInitialMessages(messages)
3584
+ }));
3585
+ const placeholder = initial.view.hidden > 0 ? [{ id: -1, role: "system", text: "", collapsed: initial.view.hidden }] : [];
3586
+ const items = [...placeholder, ...initial.view.visible, ...messages.slice(initial.length)];
3587
+ return /* @__PURE__ */ jsx7(Static, { items, children: (message) => {
3588
+ if (message.collapsed !== void 0) {
3589
+ return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
3590
+ "\u2026 ",
3591
+ message.collapsed,
3592
+ " earlier messages hidden \u2014 full history in the session file"
3593
+ ] }) }, message.id);
3594
+ }
3416
3595
  const marginBottom = message.tight ? 0 : 1;
3417
3596
  if (message.role === "assistant-cont") {
3418
3597
  return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom, children: /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
@@ -3437,7 +3616,7 @@ var MessageList = memo(function MessageList2({ messages }) {
3437
3616
  }
3438
3617
  if (message.role === "tool") {
3439
3618
  return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginBottom, children: [
3440
- /* @__PURE__ */ jsx7(Text7, { color: "magenta", children: message.text }),
3619
+ /* @__PURE__ */ jsx7(Text7, { color: message.dim ? void 0 : "magenta", dimColor: message.dim, children: message.text }),
3441
3620
  message.diff && /* @__PURE__ */ jsx7(DiffLines, { lines: message.diff })
3442
3621
  ] }, message.id);
3443
3622
  }
@@ -3527,73 +3706,7 @@ function RewindConfirmPrompt({
3527
3706
 
3528
3707
  // src/cli/components/StatusBar.tsx
3529
3708
  import { Box as Box9, Text as Text10, useStdout as useStdout3 } from "ink";
3530
- import { memo as memo2 } from "react";
3531
-
3532
- // src/cli/icons.ts
3533
- var EMOJI_TERM_PROGRAMS = /* @__PURE__ */ new Set([
3534
- "vscode",
3535
- "WezTerm",
3536
- "iTerm.app",
3537
- "Apple_Terminal",
3538
- "ghostty",
3539
- "kitty",
3540
- "Hyper",
3541
- "WarpTerminal"
3542
- ]);
3543
- function resolveIconMode(env = process.env) {
3544
- const override = env.STAR_ICONS;
3545
- if (override === "emoji" || override === "plain") return override;
3546
- if (env.WT_SESSION) return "emoji";
3547
- if (EMOJI_TERM_PROGRAMS.has(env.TERM_PROGRAM ?? "")) return "emoji";
3548
- const term = env.TERM ?? "";
3549
- if (term.includes("kitty") || term.includes("alacritty")) return "emoji";
3550
- return "plain";
3551
- }
3552
- var EMOJI_TOOL_ICONS = {
3553
- bash: "\u{1F4BB}",
3554
- read_file: "\u{1F4C4}",
3555
- write_file: "\u{1F4DD}",
3556
- edit_file: "\u270F\uFE0F",
3557
- glob: "\u{1F4C2}",
3558
- grep: "\u{1F50D}",
3559
- web_search: "\u{1F50E}",
3560
- web_fetch: "\u{1F310}",
3561
- todo_write: "\u{1F4CB}",
3562
- todo_read: "\u{1F4CB}",
3563
- task_list: "\u{1F9F5}",
3564
- task_output: "\u{1F9F5}",
3565
- task_kill: "\u{1F6D1}",
3566
- subagent: "\u{1F916}",
3567
- skill: "\u2728",
3568
- remember: "\u{1F9E0}"
3569
- };
3570
- var PLAIN_TOOL_ICONS = {
3571
- bash: "$",
3572
- read_file: "\u2261",
3573
- write_file: "+",
3574
- edit_file: "\u270E",
3575
- glob: "*",
3576
- grep: "/",
3577
- web_search: "?",
3578
- web_fetch: "\u21D3",
3579
- todo_write: "\u2023",
3580
- todo_read: "\u2023",
3581
- task_list: "\xB6",
3582
- task_output: "\xB6",
3583
- task_kill: "\xD7",
3584
- subagent: "\u25C9",
3585
- skill: "\u2727",
3586
- remember: "\u203B"
3587
- };
3588
- var iconMode = resolveIconMode();
3589
- function toolIcon(name) {
3590
- const map = iconMode === "emoji" ? EMOJI_TOOL_ICONS : PLAIN_TOOL_ICONS;
3591
- return map[name] ?? (iconMode === "emoji" ? "\u{1F527}" : "\u25B8");
3592
- }
3593
- var thinkingIcon = iconMode === "emoji" ? "\u{1F4AD}" : "\u2727";
3594
- var folderIcon = iconMode === "emoji" ? "\u{1F4C1} " : "";
3595
-
3596
- // src/cli/components/StatusBar.tsx
3709
+ import { memo as memo3 } from "react";
3597
3710
  import { jsxs as jsxs10 } from "react/jsx-runtime";
3598
3711
  var BG_LABEL_MAX = 40;
3599
3712
  var WIDE_COLUMNS = 120;
@@ -3602,7 +3715,7 @@ function cwdLabel(cwd, wide) {
3602
3715
  const base = cwd.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
3603
3716
  return base && base.length > 0 ? base : cwd;
3604
3717
  }
3605
- var StatusBar = memo2(function StatusBar2({
3718
+ var StatusBar = memo3(function StatusBar2({
3606
3719
  cwd,
3607
3720
  model,
3608
3721
  permissionMode,
@@ -3660,9 +3773,9 @@ var StatusBar = memo2(function StatusBar2({
3660
3773
 
3661
3774
  // src/cli/components/StreamingMessage.tsx
3662
3775
  import { Box as Box10, Text as Text11, useStdout as useStdout4 } from "ink";
3663
- import { memo as memo3 } from "react";
3776
+ import { memo as memo4 } from "react";
3664
3777
  import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
3665
- var StreamingMessage = memo3(function StreamingMessage2({
3778
+ var StreamingMessage = memo4(function StreamingMessage2({
3666
3779
  text,
3667
3780
  continuation
3668
3781
  }) {
@@ -3675,6 +3788,24 @@ var StreamingMessage = memo3(function StreamingMessage2({
3675
3788
 
3676
3789
  // src/cli/components/ThinkingIndicator.tsx
3677
3790
  import { Box as Box11, Text as Text12 } from "ink";
3791
+ import { memo as memo5, useEffect as useEffect3, useState as useState6 } from "react";
3792
+
3793
+ // src/cli/ticker.ts
3794
+ var TICK_MS = 100;
3795
+ function nextFlush(prev, next) {
3796
+ if (prev.streamed === next.streamed && prev.reasoning === next.reasoning) return null;
3797
+ return next;
3798
+ }
3799
+ function startTicker(onTick, intervalMs = TICK_MS) {
3800
+ let tick = 0;
3801
+ const timer = setInterval(() => {
3802
+ tick += 1;
3803
+ onTick(tick);
3804
+ }, intervalMs);
3805
+ return () => clearInterval(timer);
3806
+ }
3807
+
3808
+ // src/cli/components/ThinkingIndicator.tsx
3678
3809
  import { jsxs as jsxs12 } from "react/jsx-runtime";
3679
3810
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3680
3811
  var REASONING_TAIL_LENGTH = 200;
@@ -3684,15 +3815,18 @@ function truncateTail(text, max) {
3684
3815
  if (flat.length <= max) return flat;
3685
3816
  return `\u2026${flat.slice(flat.length - max)}`;
3686
3817
  }
3687
- function ThinkingIndicator({
3818
+ var ThinkingIndicator = memo5(function ThinkingIndicator2({
3688
3819
  reasoning,
3689
- frame = 0,
3820
+ frame,
3690
3821
  activity,
3691
- elapsedSec
3822
+ startedAt
3692
3823
  }) {
3824
+ const [tick, setTick] = useState6(0);
3825
+ useEffect3(() => startTicker((t) => setTick(t)), []);
3693
3826
  const tail = reasoning ? truncateTail(reasoning, REASONING_TAIL_LENGTH) : "";
3694
- const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
3827
+ const spinner = SPINNER_FRAMES[(frame ?? tick) % SPINNER_FRAMES.length];
3695
3828
  const label = activity ?? `${thinkingIcon} star is thinking\u2026`;
3829
+ const elapsedSec = startedAt === void 0 ? void 0 : Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
3696
3830
  const elapsed = elapsedSec !== void 0 && elapsedSec >= 3 ? ` (${formatElapsedSeconds(elapsedSec)})` : "";
3697
3831
  return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
3698
3832
  /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
@@ -3706,10 +3840,11 @@ function ThinkingIndicator({
3706
3840
  tail
3707
3841
  ] })
3708
3842
  ] });
3709
- }
3843
+ });
3710
3844
 
3711
3845
  // src/cli/components/TodoPanel.tsx
3712
3846
  import { Box as Box12, Text as Text13 } from "ink";
3847
+ import { memo as memo6 } from "react";
3713
3848
  import { jsxs as jsxs13 } from "react/jsx-runtime";
3714
3849
  var SYMBOLS = {
3715
3850
  pending: "\u25CB",
@@ -3728,7 +3863,7 @@ function visibleTodoWindow(todos, max = MAX_VISIBLE_TODOS) {
3728
3863
  hiddenAfter: todos.length - start - items.length
3729
3864
  };
3730
3865
  }
3731
- function TodoPanel({ todos }) {
3866
+ var TodoPanel = memo6(function TodoPanel2({ todos }) {
3732
3867
  if (todos.length === 0 || todos.every((t) => t.status === "done")) return null;
3733
3868
  const { items, hiddenBefore, hiddenAfter } = visibleTodoWindow(todos);
3734
3869
  return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
@@ -3757,7 +3892,7 @@ function TodoPanel({ todos }) {
3757
3892
  " more"
3758
3893
  ] })
3759
3894
  ] });
3760
- }
3895
+ });
3761
3896
 
3762
3897
  // src/cli/components/ToolCallCard.tsx
3763
3898
  function formatToolCard(card) {
@@ -3775,20 +3910,20 @@ function isDoubleEscape(previous, now, windowMs = DOUBLE_ESC_WINDOW_MS) {
3775
3910
  }
3776
3911
 
3777
3912
  // src/cli/history.ts
3778
- import fs6 from "fs";
3779
- import path12 from "path";
3913
+ import fs7 from "fs";
3914
+ import path13 from "path";
3780
3915
  var HISTORY_LIMIT = 50;
3781
3916
  var JUNK_ENTRY_PATTERN = /^(.)\1{19,}$/;
3782
3917
  function historyPath() {
3783
- return path12.join(starHome(), "history");
3918
+ return path13.join(starHome(), "history");
3784
3919
  }
3785
3920
  function readLines(file) {
3786
- const raw = fs6.readFileSync(file, "utf8");
3921
+ const raw = fs7.readFileSync(file, "utf8");
3787
3922
  return raw.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.trim().length > 0);
3788
3923
  }
3789
3924
  function writeHistory(file, lines) {
3790
- fs6.mkdirSync(path12.dirname(file), { recursive: true });
3791
- fs6.writeFileSync(file, `${lines.join("\n")}
3925
+ fs7.mkdirSync(path13.dirname(file), { recursive: true });
3926
+ fs7.writeFileSync(file, `${lines.join("\n")}
3792
3927
  `);
3793
3928
  }
3794
3929
  function isJunkHistoryEntry(line) {
@@ -3825,8 +3960,8 @@ function appendHistory(entry) {
3825
3960
  writeHistory(file, [...existing.slice(-(HISTORY_LIMIT - 1)), line]);
3826
3961
  return;
3827
3962
  }
3828
- fs6.mkdirSync(path12.dirname(file), { recursive: true });
3829
- fs6.appendFileSync(file, `${line}
3963
+ fs7.mkdirSync(path13.dirname(file), { recursive: true });
3964
+ fs7.appendFileSync(file, `${line}
3830
3965
  `);
3831
3966
  } catch {
3832
3967
  }
@@ -3900,27 +4035,38 @@ async function executeShellBang(raw, cwd, signal) {
3900
4035
  };
3901
4036
  }
3902
4037
 
3903
- // src/cli/ticker.ts
3904
- var TICK_MS = 100;
3905
- function nextFlush(prev, next) {
3906
- if (prev.streamed === next.streamed && prev.reasoning === next.reasoning) return null;
3907
- return next;
3908
- }
3909
- function startTicker(onTick, intervalMs = TICK_MS) {
3910
- let tick = 0;
3911
- const timer = setInterval(() => {
3912
- tick += 1;
3913
- onTick(tick);
3914
- }, intervalMs);
3915
- return () => clearInterval(timer);
3916
- }
3917
-
3918
4038
  // src/cli/update-check.ts
4039
+ import { mkdir, readFile as readFile4, rename, writeFile } from "fs/promises";
4040
+ import path14 from "path";
3919
4041
  var REGISTRY_URL2 = "https://registry.npmjs.org/@cryer%2fstar-cli/latest";
3920
4042
  var TIMEOUT_MS = 5e3;
4043
+ var THROTTLE_MS = 24 * 60 * 60 * 1e3;
3921
4044
  async function defaultFetcher2(url) {
3922
4045
  return fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
3923
4046
  }
4047
+ function defaultStatePath() {
4048
+ return path14.join(starHome(), "update-check.json");
4049
+ }
4050
+ async function readState(statePath) {
4051
+ try {
4052
+ const raw = JSON.parse(await readFile4(statePath, "utf8"));
4053
+ if (typeof raw !== "object" || raw === null) return null;
4054
+ const { lastCheckAt, latest } = raw;
4055
+ if (typeof lastCheckAt !== "number" || !Number.isFinite(lastCheckAt)) return null;
4056
+ return { lastCheckAt, latest: typeof latest === "string" ? latest : null };
4057
+ } catch {
4058
+ return null;
4059
+ }
4060
+ }
4061
+ async function writeState(statePath, state) {
4062
+ try {
4063
+ await mkdir(path14.dirname(statePath), { recursive: true });
4064
+ const tmp = `${statePath}.tmp-${process.pid}`;
4065
+ await writeFile(tmp, JSON.stringify(state), "utf8");
4066
+ await rename(tmp, statePath);
4067
+ } catch {
4068
+ }
4069
+ }
3924
4070
  function isNewerVersion(latest, current) {
3925
4071
  const parse2 = (v) => v.split(".").slice(0, 3).map((part) => Number.parseInt(part, 10));
3926
4072
  const a = parse2(latest);
@@ -3933,14 +4079,25 @@ function isNewerVersion(latest, current) {
3933
4079
  }
3934
4080
  return false;
3935
4081
  }
3936
- async function checkForUpdate(currentVersion, fetcher = defaultFetcher2) {
4082
+ function noticeFor(latest, currentVersion) {
4083
+ if (typeof latest !== "string" || !isNewerVersion(latest, currentVersion)) return null;
4084
+ return `New version available: ${currentVersion} -> ${latest} \u2014 run: npm i -g @cryer/star-cli`;
4085
+ }
4086
+ async function checkForUpdate(currentVersion, fetcher = defaultFetcher2, statePath = defaultStatePath()) {
3937
4087
  try {
4088
+ const state = await readState(statePath);
4089
+ if (state && Date.now() - state.lastCheckAt < THROTTLE_MS) {
4090
+ return noticeFor(state.latest, currentVersion);
4091
+ }
3938
4092
  const res = await fetcher(REGISTRY_URL2);
3939
4093
  if (!res.ok) return null;
3940
4094
  const data = await res.json();
3941
4095
  const latest = typeof data === "object" && data !== null ? data.version : null;
3942
- if (typeof latest !== "string" || !isNewerVersion(latest, currentVersion)) return null;
3943
- return `New version available: ${currentVersion} -> ${latest} \u2014 run: npm i -g @cryer/star-cli`;
4096
+ await writeState(statePath, {
4097
+ lastCheckAt: Date.now(),
4098
+ latest: typeof latest === "string" ? latest : null
4099
+ });
4100
+ return noticeFor(typeof latest === "string" ? latest : null, currentVersion);
3944
4101
  } catch {
3945
4102
  return null;
3946
4103
  }
@@ -3989,30 +4146,30 @@ function Repl({
3989
4146
  initialUsage
3990
4147
  }) {
3991
4148
  const { exit } = useApp();
3992
- const [initialDisplay] = useState5(() => buildDisplayMessages(initialMessages ?? []));
3993
- const [messages, setMessages] = useState5(initialDisplay);
3994
- const [epoch, setEpoch] = useState5(0);
3995
- const [streamingText, setStreamingText] = useState5(null);
3996
- const [isStreaming, setIsStreaming] = useState5(false);
3997
- const [thinking, setThinking] = useState5(false);
3998
- const [thinkingText, setThinkingText] = useState5("");
3999
- const [thoughtSummary, setThoughtSummary] = useState5(null);
4000
- const [, setUsageVersion] = useState5(0);
4001
- const [modelName, setModelName] = useState5(model);
4002
- const [pending, setPending] = useState5(null);
4003
- const [spinnerTick, setSpinnerTick] = useState5(0);
4004
- const [activity, setActivity] = useState5(null);
4005
- const [bgLabels, setBgLabels] = useState5(() => runningTaskLabels());
4006
- const [todos, setTodos] = useState5([]);
4007
- const [permissionModeState, setPermissionModeState] = useState5(permissionMode);
4008
- const [planApproval, setPlanApprovalState] = useState5(false);
4009
- const [pendingRewind, setPendingRewindState] = useState5(null);
4010
- const [picker, setPickerState] = useState5(null);
4011
- const [pendingConnect, setPendingConnectState] = useState5(null);
4149
+ const [initialDisplay] = useState7(() => buildDisplayMessages(initialMessages ?? []));
4150
+ const [messages, setMessages] = useState7(initialDisplay);
4151
+ const [epoch, setEpoch] = useState7(0);
4152
+ const [streamingText, setStreamingText] = useState7(null);
4153
+ const [isStreaming, setIsStreaming] = useState7(false);
4154
+ const [thinking, setThinking] = useState7(false);
4155
+ const [thinkingText, setThinkingText] = useState7("");
4156
+ const [thoughtSummary, setThoughtSummary] = useState7(null);
4157
+ const [, setUsageVersion] = useState7(0);
4158
+ const [modelName, setModelName] = useState7(model);
4159
+ const [pending, setPending] = useState7(null);
4160
+ const [activity, setActivity] = useState7(null);
4161
+ const [bgLabels, setBgLabels] = useState7(() => runningTaskLabels());
4162
+ const [todos, setTodos] = useState7([]);
4163
+ const [permissionModeState, setPermissionModeState] = useState7(permissionMode);
4164
+ const [planApproval, setPlanApprovalState] = useState7(false);
4165
+ const [pendingRewind, setPendingRewindState] = useState7(null);
4166
+ const [picker, setPickerState] = useState7(null);
4167
+ const [pendingConnect, setPendingConnectState] = useState7(null);
4012
4168
  const backendRef = useRef6(backend);
4013
4169
  const nextIdRef = useRef6(initialDisplay.length);
4014
4170
  const abortRef = useRef6(null);
4015
4171
  const busyCommandRef = useRef6(null);
4172
+ const busyAbortRef = useRef6(null);
4016
4173
  const streamedRef = useRef6("");
4017
4174
  const turnStartedAtRef = useRef6(0);
4018
4175
  const lastCommittedLenRef = useRef6(0);
@@ -4024,6 +4181,7 @@ function Repl({
4024
4181
  const pendingRef = useRef6(null);
4025
4182
  const permissionChainRef = useRef6(Promise.resolve());
4026
4183
  const alwaysAllowedRef = useRef6(/* @__PURE__ */ new Set());
4184
+ const alwaysAllowedListRef = useRef6([]);
4027
4185
  const prevModeRef = useRef6(null);
4028
4186
  const planApprovalRef = useRef6(false);
4029
4187
  const pendingRewindRef = useRef6(null);
@@ -4032,23 +4190,23 @@ function Repl({
4032
4190
  const modelNameRef = useRef6(model);
4033
4191
  const sessionStoreRef = useRef6(sessionStore);
4034
4192
  const configRef = useRef6(config);
4035
- useEffect3(() => {
4193
+ useEffect4(() => {
4036
4194
  configRef.current = config;
4037
4195
  }, [config]);
4038
4196
  const usageRef = useRef6(initialUsage ? { ...initialUsage } : emptyUsage());
4039
4197
  const turnChunksRef = useRef6(0);
4040
4198
  const messagesRef = useRef6(initialDisplay);
4041
- const [queueItems, setQueueItems] = useState5([]);
4199
+ const [queueItems, setQueueItems] = useState7([]);
4042
4200
  const queueRef = useRef6(new PromptQueue());
4043
- const [pendingImages, setPendingImages] = useState5([]);
4201
+ const [pendingImages, setPendingImages] = useState7([]);
4044
4202
  const pendingImagesRef = useRef6([]);
4045
4203
  const imageSeqRef = useRef6(1);
4046
- const [inputRefill, setInputRefill] = useState5();
4204
+ const [inputRefill, setInputRefill] = useState7();
4047
4205
  const refillSeqRef = useRef6(0);
4048
4206
  const lastEscRef = useRef6(null);
4049
- const [gitBranch, setGitBranch] = useState5(null);
4050
- const [contextPercent, setContextPercent] = useState5(null);
4051
- const [initialHistory] = useState5(() => loadHistory());
4207
+ const [gitBranch, setGitBranch] = useState7(null);
4208
+ const [contextPercent, setContextPercent] = useState7(null);
4209
+ const [initialHistory] = useState7(() => loadHistory());
4052
4210
  const budgetWarnedRef = useRef6(false);
4053
4211
  const budgetExceededRef = useRef6(false);
4054
4212
  const runStreamRef = useRef6(
@@ -4214,7 +4372,7 @@ function Repl({
4214
4372
  const attachConfirmHandler = useCallback(
4215
4373
  (target) => {
4216
4374
  target.confirmHandler = (req) => {
4217
- if (isAllowedByRules([...alwaysAllowedRef.current], req)) {
4375
+ if (isAllowedByRules(alwaysAllowedListRef.current, req)) {
4218
4376
  return Promise.resolve(true);
4219
4377
  }
4220
4378
  const ask = async () => {
@@ -4234,7 +4392,7 @@ function Repl({
4234
4392
  },
4235
4393
  [setPendingPermission, cwd, pushMessage]
4236
4394
  );
4237
- useEffect3(() => {
4395
+ useEffect4(() => {
4238
4396
  attachConfirmHandler(backendRef.current);
4239
4397
  return () => {
4240
4398
  tickerStopRef.current?.();
@@ -4242,19 +4400,19 @@ function Repl({
4242
4400
  pendingRef.current?.resolve(false);
4243
4401
  };
4244
4402
  }, [attachConfirmHandler]);
4245
- useEffect3(() => {
4403
+ useEffect4(() => {
4246
4404
  if (process.env.STAR_NO_UPDATE_CHECK === "1") return;
4247
4405
  void checkForUpdate(VERSION).then((message) => {
4248
4406
  if (message) pushMessage("system", message);
4249
4407
  });
4250
4408
  }, [pushMessage]);
4251
4409
  const resumedAtStart = initialMessages !== void 0;
4252
- useEffect3(() => {
4410
+ useEffect4(() => {
4253
4411
  if (!resumedAtStart) return;
4254
- loadTodos(cwd).then(setTodos).catch(() => {
4412
+ loadTodos(cwd, sessionStoreRef.current?.id).then(setTodos).catch(() => {
4255
4413
  });
4256
4414
  }, [cwd, resumedAtStart]);
4257
- useEffect3(() => {
4415
+ useEffect4(() => {
4258
4416
  const onUpdate = (task) => {
4259
4417
  setBgLabels(runningTaskLabels());
4260
4418
  pushMessage(
@@ -4348,6 +4506,11 @@ function Repl({
4348
4506
  lastEscRef.current = null;
4349
4507
  return;
4350
4508
  }
4509
+ if (busyCommandRef.current) {
4510
+ lastEscRef.current = null;
4511
+ busyAbortRef.current?.abort();
4512
+ return;
4513
+ }
4351
4514
  const busy = abortRef.current !== null || pendingRef.current !== null;
4352
4515
  if (busy) {
4353
4516
  lastEscRef.current = null;
@@ -4365,7 +4528,7 @@ function Repl({
4365
4528
  }
4366
4529
  lastEscRef.current = null;
4367
4530
  if (key.shift && key.tab) {
4368
- if (abortRef.current || pendingRef.current || planApprovalRef.current || pendingRewindRef.current || pendingConnectRef.current || pickerRef.current)
4531
+ if (abortRef.current || pendingRef.current || busyCommandRef.current || planApprovalRef.current || pendingRewindRef.current || pendingConnectRef.current || pickerRef.current)
4369
4532
  return;
4370
4533
  const current = config.permissionMode;
4371
4534
  const index = SESSION_MODE_CYCLE.indexOf(current);
@@ -4380,6 +4543,7 @@ function Repl({
4380
4543
  if (decision === "always") {
4381
4544
  const rule = buildAllowRule(p.request);
4382
4545
  alwaysAllowedRef.current.add(rule);
4546
+ alwaysAllowedListRef.current = [...alwaysAllowedRef.current];
4383
4547
  void addAllowRule(rule).then((added) => {
4384
4548
  pushMessage(
4385
4549
  "system",
@@ -4410,11 +4574,13 @@ function Repl({
4410
4574
  providerMetadata: reasoningEffortMetadata(config, name),
4411
4575
  temperature: modelConfig.temperature,
4412
4576
  streamIdleTimeoutSec: modelConfig.streamIdleTimeoutSec,
4413
- streamFirstChunkTimeoutSec: modelConfig.streamFirstChunkTimeoutSec
4577
+ streamFirstChunkTimeoutSec: modelConfig.streamFirstChunkTimeoutSec,
4578
+ vision: modelConfig.vision,
4579
+ modelName: name
4414
4580
  });
4415
4581
  const prev = backendRef.current;
4416
4582
  if (prev instanceof AgentLoop) {
4417
- await loop.loadMessages([...prev.getMessages()]);
4583
+ await loop.loadMessages([...prev.getStarMessages()]);
4418
4584
  }
4419
4585
  attachConfirmHandler(loop);
4420
4586
  backendRef.current = loop;
@@ -4495,9 +4661,10 @@ function Repl({
4495
4661
  await current.loadMessages(resumed.messages);
4496
4662
  current.setSessionStore(store);
4497
4663
  sessionStoreRef.current = store;
4498
- setTodos(await loadTodos(cwd));
4664
+ setTodoSession(store.id);
4665
+ setTodos(await loadTodos(cwd, store.id));
4499
4666
  hydrateSnapshots(await loadSessionSnapshots(store.dir));
4500
- const display = buildDisplayMessages(resumed.messages);
4667
+ const display = buildDisplayMessages(toCoreMessages(resumed.messages));
4501
4668
  nextIdRef.current = display.length;
4502
4669
  applyMessages(display);
4503
4670
  usageRef.current = resumed.meta.usage ? { ...resumed.meta.usage } : emptyUsage();
@@ -4544,8 +4711,7 @@ function Repl({
4544
4711
  flushedRef.current = { ...flushedRef.current, streamed: "" };
4545
4712
  setStreamingText("");
4546
4713
  };
4547
- tickerStopRef.current = startTicker((tick) => {
4548
- setSpinnerTick(tick);
4714
+ tickerStopRef.current = startTicker(() => {
4549
4715
  const uncommitted = streamedRef.current.slice(lastCommittedLenRef.current);
4550
4716
  const split = splitCommittableLines(uncommitted, 8);
4551
4717
  if (split) {
@@ -4761,7 +4927,7 @@ function Repl({
4761
4927
  sessionCostUsd
4762
4928
  ]
4763
4929
  );
4764
- useEffect3(() => {
4930
+ useEffect4(() => {
4765
4931
  runStreamRef.current = runStream;
4766
4932
  }, [runStream]);
4767
4933
  const handlePlanDecision = useCallback(
@@ -4919,7 +5085,7 @@ ${lines.join("\n")}`;
4919
5085
  if (!(current instanceof AgentLoop) || !store) {
4920
5086
  return "Nothing to fork \u2014 no active session.";
4921
5087
  }
4922
- const messages2 = await store.messages();
5088
+ const messages2 = await store.starMessages();
4923
5089
  if (messages2.length === 0) {
4924
5090
  return "Nothing to fork \u2014 the current session has no messages yet.";
4925
5091
  }
@@ -4930,6 +5096,7 @@ ${lines.join("\n")}`;
4930
5096
  await fork.setTitle(title);
4931
5097
  current.setSessionStore(fork);
4932
5098
  sessionStoreRef.current = fork;
5099
+ setTodoSession(fork.id);
4933
5100
  return `Forked into new session ${fork.id} ("${title}") and switched to it. Checkpoints and rewind history do not carry over.`;
4934
5101
  },
4935
5102
  newSession: async () => {
@@ -4941,8 +5108,9 @@ ${lines.join("\n")}`;
4941
5108
  const store = await SessionStore.create(cwd, modelNameRef.current);
4942
5109
  current.setSessionStore(store);
4943
5110
  sessionStoreRef.current = store;
4944
- const [first] = current.getMessages();
4945
- await current.loadMessages(first?.role === "system" ? [first] : []);
5111
+ setTodoSession(store.id);
5112
+ const [first] = current.getStarMessages();
5113
+ await current.loadMessages(first?.message.role === "system" ? [first] : []);
4946
5114
  clearSnapshots();
4947
5115
  redrawMessages([]);
4948
5116
  resetTodos();
@@ -4987,14 +5155,16 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
4987
5155
  return `Busy \u2014 /${busyCommandRef.current} is still running.`;
4988
5156
  }
4989
5157
  busyCommandRef.current = "compact";
5158
+ const controller = new AbortController();
5159
+ busyAbortRef.current = controller;
4990
5160
  turnStartedAtRef.current = Date.now();
4991
- tickerStopRef.current = startTicker((tick) => setSpinnerTick(tick));
4992
5161
  setActivity("compacting context\u2026");
4993
5162
  try {
4994
5163
  const result = await compactSession({
4995
5164
  backend: backendRef.current,
4996
5165
  sessionStore: sessionStoreRef.current,
4997
- config
5166
+ config,
5167
+ signal: controller.signal
4998
5168
  });
4999
5169
  if (result.compacted && result.messages) {
5000
5170
  const display = buildDisplayMessages(result.messages);
@@ -5007,6 +5177,7 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
5007
5177
  tickerStopRef.current = null;
5008
5178
  setActivity(null);
5009
5179
  busyCommandRef.current = null;
5180
+ busyAbortRef.current = null;
5010
5181
  const nextPrompt = queueRef.current.dequeue();
5011
5182
  if (nextPrompt) {
5012
5183
  setQueueItems(queueRef.current.list());
@@ -5200,6 +5371,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5200
5371
  };
5201
5372
  const reg = new CommandRegistry();
5202
5373
  registerBuiltinCommands(reg);
5374
+ const modelCommand = reg.get("model");
5375
+ if (modelCommand) modelCommand.argHints = () => config.models.map((m) => m.name);
5203
5376
  registerCustomCommands(reg, cwd);
5204
5377
  return Object.assign(reg, { ctx });
5205
5378
  }, [
@@ -5318,16 +5491,17 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5318
5491
  () => registry.list().map((cmd) => ({
5319
5492
  name: cmd.name,
5320
5493
  description: cmd.description,
5321
- usage: cmd.usage ?? `/${cmd.name}`
5494
+ usage: cmd.usage ?? `/${cmd.name}`,
5495
+ argHints: cmd.argHints
5322
5496
  })),
5323
5497
  [registry]
5324
5498
  );
5325
- useEffect3(() => {
5499
+ useEffect4(() => {
5326
5500
  setGitBranch(getGitSummaryCached(cwd)?.branch ?? null);
5327
5501
  const current = backendRef.current;
5328
5502
  if (current instanceof AgentLoop) {
5329
5503
  const window_ = contextWindowTokens(config, modelNameRef.current);
5330
- const pct = estimateTokens([...current.getMessages()]) / window_ * 100;
5504
+ const pct = (estimateTokens(current.getMessages()) + current.getToolSchemaTokens()) / window_ * 100;
5331
5505
  setContextPercent(pct < 10 ? Math.round(pct * 10) / 10 : Math.round(pct));
5332
5506
  } else {
5333
5507
  setContextPercent(null);
@@ -5340,9 +5514,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5340
5514
  ThinkingIndicator,
5341
5515
  {
5342
5516
  reasoning: thinkingText,
5343
- frame: spinnerTick,
5344
5517
  activity: activity ?? void 0,
5345
- elapsedSec: Math.max(0, Math.floor((Date.now() - turnStartedAtRef.current) / 1e3))
5518
+ startedAt: turnStartedAtRef.current
5346
5519
  }
5347
5520
  ),
5348
5521
  !thinking && activity === null && thoughtSummary !== null && /* @__PURE__ */ jsx11(Text14, { dimColor: true, children: thoughtSummary }),
@@ -5461,4 +5634,4 @@ export {
5461
5634
  Repl,
5462
5635
  renderRepl
5463
5636
  };
5464
- //# sourceMappingURL=repl-YA23CK5R.js.map
5637
+ //# sourceMappingURL=repl-OO7C6V7M.js.map