@cryer/star-cli 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -5
- package/README.zh-CN.md +15 -5
- package/dist/{chunk-LSYGHLRU.js → chunk-2ISBRSBA.js} +479 -296
- package/dist/chunk-2ISBRSBA.js.map +1 -0
- package/dist/{chunk-F5RWZMQY.js → chunk-4HANB3DJ.js} +458 -221
- package/dist/chunk-4HANB3DJ.js.map +1 -0
- package/dist/chunk-4IRY44TG.js +0 -0
- package/dist/{chunk-Q5FJN4LN.js → chunk-54KH4JSO.js} +213 -125
- package/dist/chunk-54KH4JSO.js.map +1 -0
- package/dist/{chunk-MYHD3SZB.js → chunk-CECMCDZG.js} +43 -30
- package/dist/chunk-CECMCDZG.js.map +1 -0
- package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
- package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
- package/dist/{chunk-2GAUAYCU.js → chunk-NRLGGA7L.js} +507 -175
- package/dist/chunk-NRLGGA7L.js.map +1 -0
- package/dist/loop-M64D5YII.js +17 -0
- package/dist/main.js +177 -131
- package/dist/main.js.map +1 -1
- package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
- package/dist/{repl-SSCURTAH.js → repl-N2H7JDSH.js} +617 -327
- package/dist/repl-N2H7JDSH.js.map +1 -0
- package/dist/{tools-BH55BVIS.js → tools-5RKM7ZI3.js} +3 -3
- package/package.json +16 -25
- package/dist/chunk-2GAUAYCU.js.map +0 -1
- package/dist/chunk-F5RWZMQY.js.map +0 -1
- package/dist/chunk-LSYGHLRU.js.map +0 -1
- package/dist/chunk-MYHD3SZB.js.map +0 -1
- package/dist/chunk-Q5FJN4LN.js.map +0 -1
- package/dist/loop-BYXBNFFY.js +0 -17
- package/dist/repl-SSCURTAH.js.map +0 -1
- /package/dist/{loop-BYXBNFFY.js.map → loop-M64D5YII.js.map} +0 -0
- /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
- /package/dist/{tools-BH55BVIS.js.map → tools-5RKM7ZI3.js.map} +0 -0
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
VERSION,
|
|
6
6
|
buildDisplayMessages,
|
|
7
7
|
clearSessions,
|
|
8
|
-
computeCostUsd,
|
|
9
8
|
deleteSession,
|
|
9
|
+
displayWidth,
|
|
10
10
|
estimateCost,
|
|
11
11
|
folderIcon,
|
|
12
12
|
formatByteCount,
|
|
@@ -30,8 +30,9 @@ import {
|
|
|
30
30
|
summarizeArgs,
|
|
31
31
|
thinkingIcon,
|
|
32
32
|
toTerminalSafe,
|
|
33
|
-
toolIcon
|
|
34
|
-
|
|
33
|
+
toolIcon,
|
|
34
|
+
truncateToWidth
|
|
35
|
+
} from "./chunk-NRLGGA7L.js";
|
|
35
36
|
import {
|
|
36
37
|
AgentLoop,
|
|
37
38
|
WORKING_DIFF_MAX_LINES,
|
|
@@ -43,23 +44,24 @@ import {
|
|
|
43
44
|
compactMessages,
|
|
44
45
|
discoverSkills,
|
|
45
46
|
estimateTokens,
|
|
46
|
-
|
|
47
|
+
getGitSummaryCachedAsync,
|
|
47
48
|
isAllowedByRules,
|
|
48
49
|
isDangerousCommand,
|
|
49
50
|
isGitRepo,
|
|
50
51
|
summarizeMessages
|
|
51
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-4HANB3DJ.js";
|
|
52
53
|
import {
|
|
54
|
+
computeCostUsd,
|
|
53
55
|
contextWindowTokens,
|
|
54
56
|
coreMessageText,
|
|
55
57
|
diffTreeNames,
|
|
56
58
|
reconcileStarMessages,
|
|
57
59
|
toCoreMessages
|
|
58
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-CECMCDZG.js";
|
|
59
61
|
import {
|
|
60
62
|
createModel,
|
|
61
63
|
reasoningEffortMetadata
|
|
62
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-KILMI3S5.js";
|
|
63
65
|
import {
|
|
64
66
|
listModels,
|
|
65
67
|
resolveModelConfig
|
|
@@ -69,7 +71,7 @@ import {
|
|
|
69
71
|
formatTaskFinished,
|
|
70
72
|
formatTaskList,
|
|
71
73
|
formatTaskStarted
|
|
72
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-54KH4JSO.js";
|
|
73
75
|
import {
|
|
74
76
|
MAX_IMAGE_DIMENSION,
|
|
75
77
|
SKIP_DIRS,
|
|
@@ -98,13 +100,14 @@ import {
|
|
|
98
100
|
starHome,
|
|
99
101
|
upsertEnvContent,
|
|
100
102
|
userMemoryPath
|
|
101
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-2ISBRSBA.js";
|
|
102
104
|
|
|
103
105
|
// src/cli/repl.tsx
|
|
104
106
|
import { Box as Box13, Text as Text14, render, useApp } from "ink";
|
|
105
|
-
import { useCallback, useEffect as useEffect4, useMemo, useRef as
|
|
107
|
+
import { useCallback, useEffect as useEffect4, useMemo, useRef as useRef7, useState as useState7 } from "react";
|
|
106
108
|
|
|
107
109
|
// src/config/save.ts
|
|
110
|
+
import { randomUUID } from "crypto";
|
|
108
111
|
import fs from "fs";
|
|
109
112
|
import path from "path";
|
|
110
113
|
import { parse, stringify } from "smol-toml";
|
|
@@ -115,14 +118,34 @@ function tomlString(value) {
|
|
|
115
118
|
return JSON.stringify(value);
|
|
116
119
|
}
|
|
117
120
|
async function writeFileAtomic(filePath, content) {
|
|
118
|
-
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
119
|
-
await fs.promises.writeFile(tmpPath, content, "utf8");
|
|
120
|
-
await fs.promises.rename(tmpPath, filePath);
|
|
121
|
+
const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
121
122
|
try {
|
|
122
|
-
await fs.promises.
|
|
123
|
-
|
|
123
|
+
const handle = await fs.promises.open(tmpPath, "wx", 384);
|
|
124
|
+
try {
|
|
125
|
+
await handle.writeFile(content, "utf8");
|
|
126
|
+
await handle.chmod(384);
|
|
127
|
+
} finally {
|
|
128
|
+
await handle.close();
|
|
129
|
+
}
|
|
130
|
+
await fs.promises.rename(tmpPath, filePath);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
await fs.promises.unlink(tmpPath).catch(() => {
|
|
133
|
+
});
|
|
134
|
+
throw error;
|
|
124
135
|
}
|
|
125
136
|
}
|
|
137
|
+
var configSaveQueues = /* @__PURE__ */ new Map();
|
|
138
|
+
function serializeConfigSave(filePath, save) {
|
|
139
|
+
const previous = configSaveQueues.get(filePath) ?? Promise.resolve();
|
|
140
|
+
const next = previous.catch(() => {
|
|
141
|
+
}).then(save);
|
|
142
|
+
configSaveQueues.set(filePath, next);
|
|
143
|
+
void next.finally(() => {
|
|
144
|
+
if (configSaveQueues.get(filePath) === next) configSaveQueues.delete(filePath);
|
|
145
|
+
}).catch(() => {
|
|
146
|
+
});
|
|
147
|
+
return next;
|
|
148
|
+
}
|
|
126
149
|
function upsertPermissionRuleText(content, kind, rules, hadPermissionsTable) {
|
|
127
150
|
const newLine = `${kind} = [${rules.map(tomlString).join(", ")}]`;
|
|
128
151
|
const lines = content.split("\n");
|
|
@@ -161,28 +184,35 @@ ${newLine}
|
|
|
161
184
|
}
|
|
162
185
|
async function addPermissionRule(kind, rule) {
|
|
163
186
|
const filePath = globalConfigPath();
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
return serializeConfigSave(filePath, async () => {
|
|
188
|
+
let content = "";
|
|
189
|
+
let raw = {};
|
|
190
|
+
try {
|
|
191
|
+
content = await fs.promises.readFile(filePath, "utf8");
|
|
192
|
+
raw = parse(content);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error.code !== "ENOENT") throw error;
|
|
195
|
+
}
|
|
196
|
+
const permissions = typeof raw.permissions === "object" && raw.permissions !== null ? raw.permissions : {};
|
|
197
|
+
const rules = Array.isArray(permissions[kind]) ? permissions[kind] : [];
|
|
198
|
+
if (rules.includes(rule)) return false;
|
|
199
|
+
const nextRules = [...rules, rule];
|
|
200
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
201
|
+
const updated = upsertPermissionRuleText(
|
|
202
|
+
content,
|
|
203
|
+
kind,
|
|
204
|
+
nextRules,
|
|
205
|
+
raw.permissions !== void 0
|
|
206
|
+
);
|
|
207
|
+
if (updated !== null) {
|
|
208
|
+
await writeFileAtomic(filePath, updated);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
permissions[kind] = nextRules;
|
|
212
|
+
raw.permissions = permissions;
|
|
213
|
+
await writeFileAtomic(filePath, stringify(raw));
|
|
180
214
|
return true;
|
|
181
|
-
}
|
|
182
|
-
permissions[kind] = nextRules;
|
|
183
|
-
raw.permissions = permissions;
|
|
184
|
-
await writeFileAtomic(filePath, stringify(raw));
|
|
185
|
-
return true;
|
|
215
|
+
});
|
|
186
216
|
}
|
|
187
217
|
function upsertTopLevelKeyText(content, key, value) {
|
|
188
218
|
const newLine = `${key} = ${tomlString(value)}`;
|
|
@@ -205,14 +235,16 @@ function upsertTopLevelKeyText(content, key, value) {
|
|
|
205
235
|
}
|
|
206
236
|
async function savePermissionMode(mode) {
|
|
207
237
|
const filePath = globalConfigPath();
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
238
|
+
return serializeConfigSave(filePath, async () => {
|
|
239
|
+
let content = "";
|
|
240
|
+
try {
|
|
241
|
+
content = await fs.promises.readFile(filePath, "utf8");
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (error.code !== "ENOENT") throw error;
|
|
244
|
+
}
|
|
245
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
246
|
+
await writeFileAtomic(filePath, upsertTopLevelKeyText(content, "permissionMode", mode));
|
|
247
|
+
});
|
|
216
248
|
}
|
|
217
249
|
function unquoteTomlString(raw) {
|
|
218
250
|
if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
|
|
@@ -271,29 +303,35 @@ function setReasoningEffortInToml(content, modelName, effort) {
|
|
|
271
303
|
}
|
|
272
304
|
async function saveReasoningEffort(modelName, effort) {
|
|
273
305
|
const filePath = globalConfigPath();
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
306
|
+
return serializeConfigSave(filePath, async () => {
|
|
307
|
+
let content = "";
|
|
308
|
+
try {
|
|
309
|
+
content = await fs.promises.readFile(filePath, "utf8");
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (error.code !== "ENOENT") throw error;
|
|
312
|
+
}
|
|
313
|
+
const updated = setReasoningEffortInToml(content, modelName, effort);
|
|
314
|
+
if (updated === null) return false;
|
|
315
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
316
|
+
await writeFileAtomic(filePath, updated);
|
|
317
|
+
return true;
|
|
318
|
+
});
|
|
285
319
|
}
|
|
286
320
|
|
|
287
321
|
// src/session/resume.ts
|
|
288
322
|
async function resumeSession(id) {
|
|
289
323
|
const store = await SessionStore.open(id);
|
|
290
324
|
if (!store) return null;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
await store.replaceMessages(messages)
|
|
325
|
+
try {
|
|
326
|
+
const [meta, loaded] = await Promise.all([store.meta(), store.starMessages()]);
|
|
327
|
+
const messages = reconcileStarMessages(loaded);
|
|
328
|
+
if (messages.length !== loaded.length && !await store.replaceMessages(messages)) {
|
|
329
|
+
throw new Error(`Could not repair persisted history for session ${id}`);
|
|
330
|
+
}
|
|
331
|
+
return { meta, messages };
|
|
332
|
+
} finally {
|
|
333
|
+
await store.close();
|
|
295
334
|
}
|
|
296
|
-
return { meta, messages };
|
|
297
335
|
}
|
|
298
336
|
|
|
299
337
|
// src/session/usage.ts
|
|
@@ -357,25 +395,29 @@ function aggregateUsage(metas) {
|
|
|
357
395
|
hasLegacy = true;
|
|
358
396
|
}
|
|
359
397
|
}
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
398
|
+
const fallbackName = typeof meta.model === "string" && meta.model.trim() ? meta.model : "unknown";
|
|
399
|
+
const perModel = meta.usageByModel !== null && typeof meta.usageByModel === "object";
|
|
400
|
+
const modelTotals = perModel ? Object.entries(meta.usageByModel ?? {}) : [[fallbackName, hasTotals ? meta.usage : void 0]];
|
|
401
|
+
for (const [name, modelUsage] of modelTotals) {
|
|
402
|
+
if (!modelUsage) continue;
|
|
403
|
+
const modelBucket = bucketOf(modelUsage);
|
|
404
|
+
const entry = models.get(name) ?? {
|
|
405
|
+
model: name,
|
|
406
|
+
sessions: 0,
|
|
407
|
+
requests: 0,
|
|
408
|
+
promptTokens: 0,
|
|
409
|
+
completionTokens: 0,
|
|
410
|
+
totalTokens: 0
|
|
411
|
+
};
|
|
412
|
+
entry.sessions += 1;
|
|
413
|
+
entry.requests += perModel ? tokens(modelUsage.requests) : requestCount;
|
|
414
|
+
addTo(entry, modelBucket);
|
|
415
|
+
const cached = tokens(modelUsage.cachedPromptTokens);
|
|
416
|
+
const cacheRead = tokens(modelUsage.cacheReadInputTokens);
|
|
375
417
|
if (cached > 0) entry.cachedPromptTokens = (entry.cachedPromptTokens ?? 0) + cached;
|
|
376
418
|
if (cacheRead > 0) entry.cacheReadInputTokens = (entry.cacheReadInputTokens ?? 0) + cacheRead;
|
|
419
|
+
models.set(name, entry);
|
|
377
420
|
}
|
|
378
|
-
models.set(name, entry);
|
|
379
421
|
}
|
|
380
422
|
return {
|
|
381
423
|
sessions: metas.length,
|
|
@@ -487,7 +529,7 @@ function cacheHitPercent(totals) {
|
|
|
487
529
|
|
|
488
530
|
// src/cli/clipboard.ts
|
|
489
531
|
import { execFile } from "child_process";
|
|
490
|
-
import { randomUUID } from "crypto";
|
|
532
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
491
533
|
import { readFile, stat, unlink } from "fs/promises";
|
|
492
534
|
import os from "os";
|
|
493
535
|
import path2 from "path";
|
|
@@ -575,7 +617,7 @@ async function readClipboardTempFile(tempFile) {
|
|
|
575
617
|
return { path: "clipboard.png", mimeType: "image/png", data: buf.toString("base64") };
|
|
576
618
|
}
|
|
577
619
|
async function readClipboardImage() {
|
|
578
|
-
const tempFile = path2.join(os.tmpdir(), `star-clipboard-${
|
|
620
|
+
const tempFile = path2.join(os.tmpdir(), `star-clipboard-${randomUUID2()}.png`);
|
|
579
621
|
const hasPngpaste = process.platform === "darwin" ? await commandExists("pngpaste") : false;
|
|
580
622
|
const plan = clipboardImagePlan(process.platform, tempFile, hasPngpaste);
|
|
581
623
|
if (!plan) return null;
|
|
@@ -927,13 +969,17 @@ function formatSearchResults(hits, query) {
|
|
|
927
969
|
}
|
|
928
970
|
|
|
929
971
|
// src/cli/diff-preview.ts
|
|
930
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
972
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
931
973
|
import path5 from "path";
|
|
932
974
|
var CONTEXT_LINES = 2;
|
|
933
975
|
var MAX_HUNK_LINES = 15;
|
|
934
976
|
var MAX_TOTAL_LINES = 12;
|
|
935
977
|
var NEW_FILE_PREVIEW_LINES = 10;
|
|
936
978
|
var MAX_DP_CELLS = 25e4;
|
|
979
|
+
var MAX_DIFF_PREVIEW_CHARS = 1e6;
|
|
980
|
+
function largeDiffSummary() {
|
|
981
|
+
return [{ kind: "marker", text: "... (large diff omitted; preview work budget exceeded)" }];
|
|
982
|
+
}
|
|
937
983
|
function diffOps(oldText, newText) {
|
|
938
984
|
const a = oldText.split("\n");
|
|
939
985
|
const b = newText.split("\n");
|
|
@@ -1019,6 +1065,8 @@ function hunkRanges(ops) {
|
|
|
1019
1065
|
return ranges;
|
|
1020
1066
|
}
|
|
1021
1067
|
function buildDiffLines(oldText, newText) {
|
|
1068
|
+
if (oldText === newText) return [];
|
|
1069
|
+
if (oldText.length + newText.length > MAX_DIFF_PREVIEW_CHARS) return largeDiffSummary();
|
|
1022
1070
|
const ops = diffOps(oldText, newText);
|
|
1023
1071
|
if (ops.length === 0) return [];
|
|
1024
1072
|
const ranges = hunkRanges(ops);
|
|
@@ -1077,6 +1125,9 @@ function applyEdit(content, oldString, newString, replaceAll) {
|
|
|
1077
1125
|
return replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, () => newString);
|
|
1078
1126
|
}
|
|
1079
1127
|
function newFilePreview(content, label) {
|
|
1128
|
+
if (content.length > MAX_DIFF_PREVIEW_CHARS) {
|
|
1129
|
+
return { type: "new-file", label, lines: largeDiffSummary() };
|
|
1130
|
+
}
|
|
1080
1131
|
const all = content.split("\n");
|
|
1081
1132
|
const shown = all.slice(0, NEW_FILE_PREVIEW_LINES);
|
|
1082
1133
|
const lines = shown.map((text) => ({ kind: "add", text }));
|
|
@@ -1090,6 +1141,10 @@ async function generateDiffPreview(toolName, args, cwd) {
|
|
|
1090
1141
|
const filePath = getStringArg(args, "path");
|
|
1091
1142
|
if (filePath === void 0) return null;
|
|
1092
1143
|
const absolute = path5.resolve(cwd, filePath);
|
|
1144
|
+
const metadata = await stat2(absolute).catch(() => null);
|
|
1145
|
+
if (metadata?.isFile() && metadata.size > MAX_DIFF_PREVIEW_CHARS) {
|
|
1146
|
+
return { type: "diff", label: filePath, lines: largeDiffSummary() };
|
|
1147
|
+
}
|
|
1093
1148
|
let existing;
|
|
1094
1149
|
try {
|
|
1095
1150
|
existing = await readFile2(absolute, "utf8");
|
|
@@ -2686,7 +2741,7 @@ function SelectPrompt({ title, options, onSelect, onCancel, onDelete }) {
|
|
|
2686
2741
|
const above = start;
|
|
2687
2742
|
const below = options.length - start - visible.length;
|
|
2688
2743
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
2689
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: title }),
|
|
2744
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: toTerminalSafe(title) }),
|
|
2690
2745
|
above > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
2691
2746
|
"\u2026 ",
|
|
2692
2747
|
above,
|
|
@@ -2697,16 +2752,16 @@ function SelectPrompt({ title, options, onSelect, onCancel, onDelete }) {
|
|
|
2697
2752
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
2698
2753
|
/* @__PURE__ */ jsxs(Text, { bold: active, inverse: active, children: [
|
|
2699
2754
|
active ? "\u276F " : " ",
|
|
2700
|
-
option.label
|
|
2755
|
+
toTerminalSafe(option.label)
|
|
2701
2756
|
] }),
|
|
2702
2757
|
option.hint && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
2703
2758
|
" (",
|
|
2704
|
-
option.hint,
|
|
2759
|
+
toTerminalSafe(option.hint),
|
|
2705
2760
|
")"
|
|
2706
2761
|
] }),
|
|
2707
2762
|
option.description && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
2708
2763
|
" \u2014 ",
|
|
2709
|
-
option.description
|
|
2764
|
+
toTerminalSafe(option.description)
|
|
2710
2765
|
] })
|
|
2711
2766
|
] }, option.value);
|
|
2712
2767
|
}),
|
|
@@ -2717,7 +2772,7 @@ function SelectPrompt({ title, options, onSelect, onCancel, onDelete }) {
|
|
|
2717
2772
|
] }),
|
|
2718
2773
|
confirming ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
2719
2774
|
"Delete ",
|
|
2720
|
-
confirming.label,
|
|
2775
|
+
toTerminalSafe(confirming.label),
|
|
2721
2776
|
"? [y] yes [n] no"
|
|
2722
2777
|
] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
2723
2778
|
"[\u2191/\u2193 or j/k] move [Enter] select",
|
|
@@ -2739,6 +2794,9 @@ import { memo, useEffect as useEffect2, useRef as useRef3, useState as useState2
|
|
|
2739
2794
|
import { readdir } from "fs/promises";
|
|
2740
2795
|
import path12 from "path";
|
|
2741
2796
|
var MAX_SCAN_ENTRIES = 2e3;
|
|
2797
|
+
var PATH_SUGGEST_DEBOUNCE_MS = 100;
|
|
2798
|
+
var PATH_SUGGEST_CACHE_MS = 2e3;
|
|
2799
|
+
var PATH_SUGGEST_CACHE_LIMIT = 100;
|
|
2742
2800
|
function extractAtToken(value, cursor) {
|
|
2743
2801
|
const before = value.slice(0, cursor);
|
|
2744
2802
|
const match = /(?:^|\s)@(\S*)$/.exec(before);
|
|
@@ -2773,24 +2831,67 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
|
|
|
2773
2831
|
matches.sort((a, b) => a.path.localeCompare(b.path));
|
|
2774
2832
|
return matches.slice(0, maxSuggestions);
|
|
2775
2833
|
}
|
|
2834
|
+
var suggestionCache = /* @__PURE__ */ new Map();
|
|
2835
|
+
function cachedSuggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSuggestions = MAX_SUGGESTIONS) {
|
|
2836
|
+
const key = `${path12.resolve(cwd)}\0${token}\0${maxScanEntries}\0${maxSuggestions}`;
|
|
2837
|
+
const now = Date.now();
|
|
2838
|
+
const cached = suggestionCache.get(key);
|
|
2839
|
+
if (cached && now - cached.createdAt <= PATH_SUGGEST_CACHE_MS) {
|
|
2840
|
+
suggestionCache.delete(key);
|
|
2841
|
+
suggestionCache.set(key, cached);
|
|
2842
|
+
return cached.pending;
|
|
2843
|
+
}
|
|
2844
|
+
const pending = Promise.resolve().then(
|
|
2845
|
+
() => suggestPaths(token, cwd, maxScanEntries, maxSuggestions)
|
|
2846
|
+
);
|
|
2847
|
+
suggestionCache.delete(key);
|
|
2848
|
+
suggestionCache.set(key, { createdAt: now, pending });
|
|
2849
|
+
if (suggestionCache.size > PATH_SUGGEST_CACHE_LIMIT) {
|
|
2850
|
+
const oldest = suggestionCache.keys().next();
|
|
2851
|
+
if (!oldest.done) suggestionCache.delete(oldest.value);
|
|
2852
|
+
}
|
|
2853
|
+
return pending;
|
|
2854
|
+
}
|
|
2776
2855
|
|
|
2777
2856
|
// src/cli/components/InputBox.tsx
|
|
2778
2857
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2858
|
+
var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
2859
|
+
function previousGraphemeBoundary(value, cursor) {
|
|
2860
|
+
let previous = 0;
|
|
2861
|
+
for (const { index } of graphemeSegmenter.segment(value.slice(0, cursor))) previous = index;
|
|
2862
|
+
return previous;
|
|
2863
|
+
}
|
|
2864
|
+
function nextGraphemeBoundary(value, cursor) {
|
|
2865
|
+
const next = graphemeSegmenter.segment(value.slice(cursor))[Symbol.iterator]().next();
|
|
2866
|
+
return next.done ? value.length : cursor + next.value.segment.length;
|
|
2867
|
+
}
|
|
2868
|
+
function graphemeColumn(value, start, end) {
|
|
2869
|
+
return Array.from(graphemeSegmenter.segment(value.slice(start, end))).length;
|
|
2870
|
+
}
|
|
2871
|
+
function cursorAtGraphemeColumn(value, start, end, column) {
|
|
2872
|
+
let count = 0;
|
|
2873
|
+
for (const { index, segment } of graphemeSegmenter.segment(value.slice(start, end))) {
|
|
2874
|
+
if (count === column) return start + index;
|
|
2875
|
+
count += 1;
|
|
2876
|
+
if (count > column) return start + index + segment.length;
|
|
2877
|
+
}
|
|
2878
|
+
return end;
|
|
2879
|
+
}
|
|
2779
2880
|
function moveCursorLines(value, cursor, delta) {
|
|
2780
2881
|
const lineStart = value.lastIndexOf("\n", cursor - 1) + 1;
|
|
2781
|
-
const col =
|
|
2882
|
+
const col = graphemeColumn(value, lineStart, cursor);
|
|
2782
2883
|
if (delta === -1) {
|
|
2783
2884
|
if (lineStart === 0) return null;
|
|
2784
2885
|
const prevEnd = lineStart - 1;
|
|
2785
2886
|
const prevStart = value.lastIndexOf("\n", prevEnd - 1) + 1;
|
|
2786
|
-
return
|
|
2887
|
+
return cursorAtGraphemeColumn(value, prevStart, prevEnd, col);
|
|
2787
2888
|
}
|
|
2788
2889
|
const lineEndIndex = value.indexOf("\n", cursor);
|
|
2789
2890
|
if (lineEndIndex === -1) return null;
|
|
2790
2891
|
const nextStart = lineEndIndex + 1;
|
|
2791
2892
|
const nextEndIndex = value.indexOf("\n", nextStart);
|
|
2792
2893
|
const nextEnd = nextEndIndex === -1 ? value.length : nextEndIndex;
|
|
2793
|
-
return
|
|
2894
|
+
return cursorAtGraphemeColumn(value, nextStart, nextEnd, col);
|
|
2794
2895
|
}
|
|
2795
2896
|
function findHistoryMatch(history, query, from, step) {
|
|
2796
2897
|
const needle = query.toLowerCase();
|
|
@@ -3000,21 +3101,20 @@ var InputBox = memo(function InputBox2({
|
|
|
3000
3101
|
const suggestions = suggestionsEnabled && commands ? filterCommands(value, commands) : [];
|
|
3001
3102
|
const argHints = suggestionsEnabled && commands && cursor === value.length ? filterArgHints(value, commands) : [];
|
|
3002
3103
|
useEffect2(() => {
|
|
3003
|
-
if (!suggestionsEnabled || !cwd) {
|
|
3004
|
-
setPathSuggestions([]);
|
|
3005
|
-
return;
|
|
3006
|
-
}
|
|
3007
|
-
const token = extractAtToken(value, cursor);
|
|
3008
|
-
if (!token) {
|
|
3009
|
-
setPathSuggestions([]);
|
|
3010
|
-
return;
|
|
3011
|
-
}
|
|
3012
3104
|
let cancelled = false;
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3105
|
+
setPathSuggestions([]);
|
|
3106
|
+
const token = suggestionsEnabled && cwd ? extractAtToken(value, cursor) : null;
|
|
3107
|
+
if (!token || !cwd) return;
|
|
3108
|
+
const timer = setTimeout(() => {
|
|
3109
|
+
void cachedSuggestPaths(token.token, cwd).then((result) => {
|
|
3110
|
+
if (!cancelled) setPathSuggestions(result);
|
|
3111
|
+
}).catch(() => {
|
|
3112
|
+
if (!cancelled) setPathSuggestions([]);
|
|
3113
|
+
});
|
|
3114
|
+
}, PATH_SUGGEST_DEBOUNCE_MS);
|
|
3016
3115
|
return () => {
|
|
3017
3116
|
cancelled = true;
|
|
3117
|
+
clearTimeout(timer);
|
|
3018
3118
|
};
|
|
3019
3119
|
}, [value, cursor, cwd, suggestionsEnabled]);
|
|
3020
3120
|
const showPathSuggestions = suggestions.length === 0 && argHints.length === 0 && pathSuggestions.length > 0;
|
|
@@ -3097,7 +3197,7 @@ var InputBox = memo(function InputBox2({
|
|
|
3097
3197
|
}
|
|
3098
3198
|
const requery = (query) => setSearch({ query, match: findHistoryMatch(history, query, history.length - 1, -1) });
|
|
3099
3199
|
if (key.backspace || key.delete) {
|
|
3100
|
-
requery(search.query.slice(0,
|
|
3200
|
+
requery(search.query.slice(0, previousGraphemeBoundary(search.query, search.query.length)));
|
|
3101
3201
|
return;
|
|
3102
3202
|
}
|
|
3103
3203
|
if (key.upArrow) {
|
|
@@ -3223,13 +3323,13 @@ ${value.slice(cursor)}`, cursor + 1);
|
|
|
3223
3323
|
if (moved !== null) setCursor(moved);
|
|
3224
3324
|
return;
|
|
3225
3325
|
} else if (key.leftArrow) {
|
|
3226
|
-
setCursor((prev) =>
|
|
3326
|
+
setCursor((prev) => previousGraphemeBoundary(value, prev));
|
|
3227
3327
|
return;
|
|
3228
3328
|
} else if (key.rightArrow) {
|
|
3229
3329
|
if (suggestionCount > 0 && cursor === value.length) {
|
|
3230
3330
|
completeHighlighted();
|
|
3231
3331
|
} else {
|
|
3232
|
-
setCursor((prev) =>
|
|
3332
|
+
setCursor((prev) => nextGraphemeBoundary(value, prev));
|
|
3233
3333
|
}
|
|
3234
3334
|
return;
|
|
3235
3335
|
}
|
|
@@ -3258,17 +3358,17 @@ ${value.slice(cursor)}`, cursor + 1);
|
|
|
3258
3358
|
}
|
|
3259
3359
|
if (key.backspace || key.delete) {
|
|
3260
3360
|
if (cursor === 0) return;
|
|
3261
|
-
let
|
|
3361
|
+
let removeStart = previousGraphemeBoundary(value, cursor);
|
|
3262
3362
|
for (const match of value.matchAll(PASTE_TOKEN_RE)) {
|
|
3263
3363
|
if (match.index + match[0].length === cursor) {
|
|
3264
|
-
|
|
3364
|
+
removeStart = match.index;
|
|
3265
3365
|
const id = Number(match[1]);
|
|
3266
3366
|
pastesRef.current.delete(id);
|
|
3267
3367
|
if (lastPasteRef.current?.id === id) lastPasteRef.current = null;
|
|
3268
3368
|
break;
|
|
3269
3369
|
}
|
|
3270
3370
|
}
|
|
3271
|
-
edit(value.slice(0,
|
|
3371
|
+
edit(value.slice(0, removeStart) + value.slice(cursor), removeStart);
|
|
3272
3372
|
return;
|
|
3273
3373
|
}
|
|
3274
3374
|
if (input && !key.ctrl && !key.meta) {
|
|
@@ -3276,9 +3376,10 @@ ${value.slice(cursor)}`, cursor + 1);
|
|
|
3276
3376
|
}
|
|
3277
3377
|
});
|
|
3278
3378
|
const before = value.slice(0, cursor);
|
|
3279
|
-
const
|
|
3280
|
-
const
|
|
3281
|
-
const
|
|
3379
|
+
const nextBoundary = nextGraphemeBoundary(value, cursor);
|
|
3380
|
+
const at = value.slice(cursor, nextBoundary);
|
|
3381
|
+
const after = value.slice(nextBoundary);
|
|
3382
|
+
const cursorChar = at === "\n" || at === "" || at === PASTE_DELIM ? " " : at;
|
|
3282
3383
|
const tail = (at === "\n" ? "\n" : "") + stylePasteTokens(after);
|
|
3283
3384
|
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}`;
|
|
3284
3385
|
const menuStart = Math.min(
|
|
@@ -3800,14 +3901,43 @@ function RewindConfirmPrompt({
|
|
|
3800
3901
|
// src/cli/components/StatusBar.tsx
|
|
3801
3902
|
import { Box as Box9, Text as Text10, useStdout as useStdout3 } from "ink";
|
|
3802
3903
|
import { memo as memo3 } from "react";
|
|
3803
|
-
import {
|
|
3904
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
3804
3905
|
var BG_LABEL_MAX = 40;
|
|
3906
|
+
var BG_PER_LABEL_MAX = 24;
|
|
3805
3907
|
var WIDE_COLUMNS = 120;
|
|
3806
3908
|
function cwdLabel(cwd, wide) {
|
|
3807
3909
|
if (wide) return cwd;
|
|
3808
3910
|
const base = cwd.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
|
|
3809
3911
|
return base && base.length > 0 ? base : cwd;
|
|
3810
3912
|
}
|
|
3913
|
+
function fitStatusSegments(segments, columns) {
|
|
3914
|
+
const clean = segments.filter((s) => s != null).map((s) => toTerminalSafe(s).replaceAll("\n", " "));
|
|
3915
|
+
if (clean.length === 0) return [];
|
|
3916
|
+
const available = Math.max(0, columns - clean.length + 1);
|
|
3917
|
+
const widths = clean.map(displayWidth);
|
|
3918
|
+
const budgets = clean.map(() => 0);
|
|
3919
|
+
let active = clean.map((_, index) => index);
|
|
3920
|
+
let remaining = available;
|
|
3921
|
+
while (remaining > 0 && active.length > 0) {
|
|
3922
|
+
const share = Math.floor(remaining / active.length);
|
|
3923
|
+
if (share === 0) {
|
|
3924
|
+
for (const index of active.slice(0, remaining)) budgets[index] = (budgets[index] ?? 0) + 1;
|
|
3925
|
+
break;
|
|
3926
|
+
}
|
|
3927
|
+
let used = 0;
|
|
3928
|
+
const next = [];
|
|
3929
|
+
for (const index of active) {
|
|
3930
|
+
const grant = Math.min((widths[index] ?? 0) - (budgets[index] ?? 0), share);
|
|
3931
|
+
budgets[index] = (budgets[index] ?? 0) + grant;
|
|
3932
|
+
used += grant;
|
|
3933
|
+
if ((budgets[index] ?? 0) < (widths[index] ?? 0)) next.push(index);
|
|
3934
|
+
}
|
|
3935
|
+
if (used === 0) break;
|
|
3936
|
+
remaining -= used;
|
|
3937
|
+
active = next;
|
|
3938
|
+
}
|
|
3939
|
+
return clean.map((segment, index) => truncateToWidth(segment, budgets[index] ?? 0));
|
|
3940
|
+
}
|
|
3811
3941
|
var StatusBar = memo3(function StatusBar2({
|
|
3812
3942
|
cwd,
|
|
3813
3943
|
model,
|
|
@@ -3821,72 +3951,69 @@ var StatusBar = memo3(function StatusBar2({
|
|
|
3821
3951
|
backgroundTasks = []
|
|
3822
3952
|
}) {
|
|
3823
3953
|
const { stdout } = useStdout3();
|
|
3824
|
-
const
|
|
3825
|
-
const
|
|
3826
|
-
const
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
]
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
"
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
] }),
|
|
3862
|
-
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
3863
|
-
"mode: ",
|
|
3864
|
-
permissionMode
|
|
3865
|
-
] })
|
|
3866
|
-
] });
|
|
3954
|
+
const columns = stdout?.columns ?? 80;
|
|
3955
|
+
const wide = columns >= WIDE_COLUMNS;
|
|
3956
|
+
const cwdSegment = `${folderIcon}${cwdLabel(cwd, wide)}${gitBranch ? ` [${gitBranch}]` : ""}`;
|
|
3957
|
+
const modelSegment = `\u2726 ${model}`;
|
|
3958
|
+
const ctxSegment = contextPercent != null ? `ctx: ${contextPercent}%${contextTokens ? ` (${formatTokensCompact(contextTokens.used)}/${formatTokensCompact(contextTokens.window)})` : ""}` : null;
|
|
3959
|
+
const cacheSegment = `cache: ${cachePercent != null ? `${cachePercent}%` : "Not provided"}`;
|
|
3960
|
+
const costSegment = sessionCostUsd != null && sessionCostUsd > 0 ? `$${formatDollars(sessionCostUsd)}` : null;
|
|
3961
|
+
const tokensSegment = `${formatTokens(tokens2)} tokens`;
|
|
3962
|
+
const modeSegment = `mode: ${permissionMode}`;
|
|
3963
|
+
let bgSegment = null;
|
|
3964
|
+
if (backgroundTasks.length > 0) {
|
|
3965
|
+
const joined = backgroundTasks.map((label) => truncateToWidth(toTerminalSafe(label), BG_PER_LABEL_MAX)).join(", ");
|
|
3966
|
+
bgSegment = `bg: ${backgroundTasks.length} (${truncateToWidth(joined, BG_LABEL_MAX)})`;
|
|
3967
|
+
}
|
|
3968
|
+
const raw = [
|
|
3969
|
+
cwdSegment,
|
|
3970
|
+
bgSegment,
|
|
3971
|
+
modelSegment,
|
|
3972
|
+
ctxSegment,
|
|
3973
|
+
cacheSegment,
|
|
3974
|
+
costSegment,
|
|
3975
|
+
tokensSegment,
|
|
3976
|
+
modeSegment
|
|
3977
|
+
];
|
|
3978
|
+
const keys = ["cwd", "bg", "model", "ctx", "cache", "cost", "tokens", "mode"].filter(
|
|
3979
|
+
(_, index) => raw[index] != null
|
|
3980
|
+
);
|
|
3981
|
+
const fitted = fitStatusSegments(raw, columns);
|
|
3982
|
+
return /* @__PURE__ */ jsx10(Box9, { justifyContent: "space-between", children: fitted.map((segment, index) => /* @__PURE__ */ jsx10(
|
|
3983
|
+
Text10,
|
|
3984
|
+
{
|
|
3985
|
+
color: keys[index] === "bg" ? "yellow" : void 0,
|
|
3986
|
+
dimColor: keys[index] !== "bg",
|
|
3987
|
+
children: segment
|
|
3988
|
+
},
|
|
3989
|
+
keys[index]
|
|
3990
|
+
)) });
|
|
3867
3991
|
});
|
|
3868
3992
|
|
|
3869
3993
|
// src/cli/components/StreamingMessage.tsx
|
|
3870
3994
|
import { Box as Box10, Text as Text11, useStdout as useStdout4 } from "ink";
|
|
3871
3995
|
import { memo as memo4 } from "react";
|
|
3872
|
-
import { jsx as
|
|
3996
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3873
3997
|
var StreamingMessage = memo4(function StreamingMessage2({
|
|
3874
3998
|
text,
|
|
3875
3999
|
continuation
|
|
3876
4000
|
}) {
|
|
3877
4001
|
const { stdout } = useStdout4();
|
|
3878
|
-
return /* @__PURE__ */
|
|
3879
|
-
!continuation && /* @__PURE__ */
|
|
3880
|
-
/* @__PURE__ */
|
|
4002
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginBottom: 1, children: [
|
|
4003
|
+
!continuation && /* @__PURE__ */ jsx11(Text11, { bold: true, color: "green", children: "\u2726 star" }),
|
|
4004
|
+
/* @__PURE__ */ jsx11(Text11, { color: "green", children: renderMarkdown(text, "32", stdout.columns ?? 80) })
|
|
3881
4005
|
] });
|
|
3882
4006
|
});
|
|
3883
4007
|
|
|
3884
4008
|
// src/cli/components/ThinkingIndicator.tsx
|
|
3885
4009
|
import { Box as Box11, Text as Text12 } from "ink";
|
|
3886
|
-
import { memo as memo5, useEffect as useEffect3, useState as useState6 } from "react";
|
|
4010
|
+
import { memo as memo5, useEffect as useEffect3, useRef as useRef6, useState as useState6 } from "react";
|
|
3887
4011
|
|
|
3888
4012
|
// src/cli/ticker.ts
|
|
3889
4013
|
var TICK_MS = 100;
|
|
4014
|
+
function isMotionDisabled(env = process.env) {
|
|
4015
|
+
return env.STAR_NO_MOTION === "1";
|
|
4016
|
+
}
|
|
3890
4017
|
function nextFlush(prev, next) {
|
|
3891
4018
|
if (prev.streamed === next.streamed && prev.reasoning === next.reasoning) return null;
|
|
3892
4019
|
return next;
|
|
@@ -3901,22 +4028,58 @@ function startTicker(onTick, intervalMs = TICK_MS) {
|
|
|
3901
4028
|
}
|
|
3902
4029
|
|
|
3903
4030
|
// src/cli/components/ThinkingIndicator.tsx
|
|
3904
|
-
import { jsxs as
|
|
4031
|
+
import { jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3905
4032
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3906
4033
|
var REASONING_TAIL_LENGTH = 200;
|
|
3907
4034
|
var THOUGHT_SUMMARY_LENGTH = 100;
|
|
3908
|
-
function
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
4035
|
+
function createReasoningDisplayCache(max = REASONING_TAIL_LENGTH) {
|
|
4036
|
+
return {
|
|
4037
|
+
max,
|
|
4038
|
+
text: "",
|
|
4039
|
+
weight: 0,
|
|
4040
|
+
flatLength: 0,
|
|
4041
|
+
flatTail: "",
|
|
4042
|
+
hasContent: false,
|
|
4043
|
+
pendingSpace: false
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
function appendFlat(cache, text) {
|
|
4047
|
+
cache.flatLength += 1;
|
|
4048
|
+
cache.flatTail = `${cache.flatTail}${text}`.slice(-cache.max);
|
|
4049
|
+
}
|
|
4050
|
+
function updateReasoningDisplay(cache, text) {
|
|
4051
|
+
if (text !== cache.text) {
|
|
4052
|
+
if (!text.startsWith(cache.text)) {
|
|
4053
|
+
cache.text = "";
|
|
4054
|
+
cache.weight = 0;
|
|
4055
|
+
cache.flatLength = 0;
|
|
4056
|
+
cache.flatTail = "";
|
|
4057
|
+
cache.hasContent = false;
|
|
4058
|
+
cache.pendingSpace = false;
|
|
4059
|
+
}
|
|
4060
|
+
const appended = text.slice(cache.text.length);
|
|
4061
|
+
for (let i = 0; i < appended.length; i++) {
|
|
4062
|
+
const ch = appended[i] ?? "";
|
|
4063
|
+
const code = ch.charCodeAt(0);
|
|
4064
|
+
cache.weight += code >= 12288 && code <= 12543 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519 ? 4 : 1;
|
|
4065
|
+
if (/\s/.test(ch)) {
|
|
4066
|
+
cache.pendingSpace = cache.hasContent;
|
|
4067
|
+
} else {
|
|
4068
|
+
if (cache.pendingSpace) appendFlat(cache, " ");
|
|
4069
|
+
appendFlat(cache, ch);
|
|
4070
|
+
cache.hasContent = true;
|
|
4071
|
+
cache.pendingSpace = false;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
cache.text = text;
|
|
3918
4075
|
}
|
|
3919
|
-
return
|
|
4076
|
+
return {
|
|
4077
|
+
tail: cache.flatLength <= cache.max ? cache.flatTail : `\u2026${cache.flatTail}`,
|
|
4078
|
+
thoughtTokens: Math.ceil(cache.weight / 4)
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
function truncateTail(text, max) {
|
|
4082
|
+
return updateReasoningDisplay(createReasoningDisplayCache(max), text).tail;
|
|
3920
4083
|
}
|
|
3921
4084
|
var ThinkingIndicator = memo5(function ThinkingIndicator2({
|
|
3922
4085
|
reasoning,
|
|
@@ -3925,26 +4088,30 @@ var ThinkingIndicator = memo5(function ThinkingIndicator2({
|
|
|
3925
4088
|
startedAt
|
|
3926
4089
|
}) {
|
|
3927
4090
|
const [tick, setTick] = useState6(0);
|
|
3928
|
-
|
|
3929
|
-
|
|
4091
|
+
const noMotion = isMotionDisabled();
|
|
4092
|
+
useEffect3(() => {
|
|
4093
|
+
if (noMotion) return;
|
|
4094
|
+
return startTicker((t) => setTick(t));
|
|
4095
|
+
}, [noMotion]);
|
|
4096
|
+
const cacheRef = useRef6(createReasoningDisplayCache());
|
|
4097
|
+
const display = updateReasoningDisplay(cacheRef.current, reasoning ?? "");
|
|
3930
4098
|
const spinner = SPINNER_FRAMES[(frame ?? tick) % SPINNER_FRAMES.length];
|
|
3931
4099
|
const label = activity ?? `${thinkingIcon} star is thinking\u2026`;
|
|
3932
4100
|
const elapsedSec = startedAt === void 0 ? void 0 : Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
|
|
3933
4101
|
const stats = [];
|
|
3934
4102
|
if (elapsedSec !== void 0 && elapsedSec >= 3) stats.push(formatElapsedSeconds(elapsedSec));
|
|
3935
|
-
|
|
3936
|
-
if (thoughtTokens > 0) stats.push(`~${formatTokens(thoughtTokens)} thought`);
|
|
4103
|
+
if (display.thoughtTokens > 0) stats.push(`~${formatTokens(display.thoughtTokens)} thought`);
|
|
3937
4104
|
const status = stats.length > 0 ? ` (${stats.join(" \xB7 ")})` : "";
|
|
3938
|
-
return /* @__PURE__ */
|
|
3939
|
-
/* @__PURE__ */
|
|
4105
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
4106
|
+
/* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
|
|
3940
4107
|
spinner,
|
|
3941
4108
|
" ",
|
|
3942
4109
|
label,
|
|
3943
4110
|
status
|
|
3944
4111
|
] }),
|
|
3945
|
-
tail !== "" && /* @__PURE__ */
|
|
4112
|
+
display.tail !== "" && /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
|
|
3946
4113
|
" ",
|
|
3947
|
-
tail
|
|
4114
|
+
display.tail
|
|
3948
4115
|
] })
|
|
3949
4116
|
] });
|
|
3950
4117
|
});
|
|
@@ -3952,7 +4119,7 @@ var ThinkingIndicator = memo5(function ThinkingIndicator2({
|
|
|
3952
4119
|
// src/cli/components/TodoPanel.tsx
|
|
3953
4120
|
import { Box as Box12, Text as Text13 } from "ink";
|
|
3954
4121
|
import { memo as memo6 } from "react";
|
|
3955
|
-
import { jsxs as
|
|
4122
|
+
import { jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3956
4123
|
var SYMBOLS = {
|
|
3957
4124
|
pending: "\u25CB",
|
|
3958
4125
|
in_progress: "\u25D0",
|
|
@@ -3973,13 +4140,13 @@ function visibleTodoWindow(todos, max = MAX_VISIBLE_TODOS) {
|
|
|
3973
4140
|
var TodoPanel = memo6(function TodoPanel2({ todos }) {
|
|
3974
4141
|
if (todos.length === 0 || todos.every((t) => t.status === "done")) return null;
|
|
3975
4142
|
const { items, hiddenBefore, hiddenAfter } = visibleTodoWindow(todos);
|
|
3976
|
-
return /* @__PURE__ */
|
|
3977
|
-
hiddenBefore > 0 && /* @__PURE__ */
|
|
4143
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
|
|
4144
|
+
hiddenBefore > 0 && /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
|
|
3978
4145
|
" \u2026 ",
|
|
3979
4146
|
hiddenBefore,
|
|
3980
4147
|
" more above"
|
|
3981
4148
|
] }),
|
|
3982
|
-
items.map((todo) => /* @__PURE__ */
|
|
4149
|
+
items.map((todo) => /* @__PURE__ */ jsxs12(
|
|
3983
4150
|
Text13,
|
|
3984
4151
|
{
|
|
3985
4152
|
dimColor: todo.status === "done",
|
|
@@ -3993,7 +4160,7 @@ var TodoPanel = memo6(function TodoPanel2({ todos }) {
|
|
|
3993
4160
|
},
|
|
3994
4161
|
todo.id
|
|
3995
4162
|
)),
|
|
3996
|
-
hiddenAfter > 0 && /* @__PURE__ */
|
|
4163
|
+
hiddenAfter > 0 && /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
|
|
3997
4164
|
" \u2026 ",
|
|
3998
4165
|
hiddenAfter,
|
|
3999
4166
|
" more"
|
|
@@ -4024,13 +4191,21 @@ var JUNK_ENTRY_PATTERN = /^(.)\1{19,}$/;
|
|
|
4024
4191
|
function historyPath() {
|
|
4025
4192
|
return path13.join(starHome(), "history");
|
|
4026
4193
|
}
|
|
4027
|
-
function
|
|
4194
|
+
function readEntries(file) {
|
|
4028
4195
|
const raw = fs7.readFileSync(file, "utf8");
|
|
4029
|
-
return raw.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.trim().length > 0)
|
|
4196
|
+
return raw.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.trim().length > 0).map((line) => {
|
|
4197
|
+
if (!line.startsWith('"')) return line;
|
|
4198
|
+
try {
|
|
4199
|
+
const parsed = JSON.parse(line);
|
|
4200
|
+
return typeof parsed === "string" ? parsed : line;
|
|
4201
|
+
} catch {
|
|
4202
|
+
return line;
|
|
4203
|
+
}
|
|
4204
|
+
});
|
|
4030
4205
|
}
|
|
4031
|
-
function writeHistory(file,
|
|
4206
|
+
function writeHistory(file, entries) {
|
|
4032
4207
|
fs7.mkdirSync(path13.dirname(file), { recursive: true });
|
|
4033
|
-
fs7.writeFileSync(file, `${
|
|
4208
|
+
fs7.writeFileSync(file, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}
|
|
4034
4209
|
`);
|
|
4035
4210
|
}
|
|
4036
4211
|
function isJunkHistoryEntry(line) {
|
|
@@ -4042,7 +4217,7 @@ function isSkippableEntry(line) {
|
|
|
4042
4217
|
function loadHistory(limit = HISTORY_LIMIT) {
|
|
4043
4218
|
try {
|
|
4044
4219
|
const file = historyPath();
|
|
4045
|
-
const lines =
|
|
4220
|
+
const lines = readEntries(file);
|
|
4046
4221
|
const clean = lines.filter((line) => !isSkippableEntry(line));
|
|
4047
4222
|
if (clean.length !== lines.length || clean.length > HISTORY_LIMIT) {
|
|
4048
4223
|
writeHistory(file, clean.slice(-HISTORY_LIMIT));
|
|
@@ -4053,13 +4228,13 @@ function loadHistory(limit = HISTORY_LIMIT) {
|
|
|
4053
4228
|
}
|
|
4054
4229
|
}
|
|
4055
4230
|
function appendHistory(entry) {
|
|
4056
|
-
const line = entry.
|
|
4231
|
+
const line = entry.trim();
|
|
4057
4232
|
if (line.length === 0 || isSkippableEntry(line)) return;
|
|
4058
4233
|
try {
|
|
4059
4234
|
const file = historyPath();
|
|
4060
4235
|
let existing = [];
|
|
4061
4236
|
try {
|
|
4062
|
-
existing =
|
|
4237
|
+
existing = readEntries(file);
|
|
4063
4238
|
} catch {
|
|
4064
4239
|
}
|
|
4065
4240
|
if (existing[existing.length - 1] === line) return;
|
|
@@ -4068,7 +4243,7 @@ function appendHistory(entry) {
|
|
|
4068
4243
|
return;
|
|
4069
4244
|
}
|
|
4070
4245
|
fs7.mkdirSync(path13.dirname(file), { recursive: true });
|
|
4071
|
-
fs7.appendFileSync(file, `${line}
|
|
4246
|
+
fs7.appendFileSync(file, `${JSON.stringify(line)}
|
|
4072
4247
|
`);
|
|
4073
4248
|
} catch {
|
|
4074
4249
|
}
|
|
@@ -4168,12 +4343,15 @@ var TerminalTitle = class _TerminalTitle {
|
|
|
4168
4343
|
deps;
|
|
4169
4344
|
timer = null;
|
|
4170
4345
|
frame = 0;
|
|
4346
|
+
running = false;
|
|
4171
4347
|
static forCwd(cwd, deps = {}) {
|
|
4172
4348
|
return new _TerminalTitle(`star-cli \u2014 ${path14.basename(cwd) || cwd}`, deps);
|
|
4173
4349
|
}
|
|
4174
4350
|
start() {
|
|
4175
|
-
if (this.
|
|
4351
|
+
if (this.running) return;
|
|
4352
|
+
this.running = true;
|
|
4176
4353
|
this.render(SPINNER_FRAMES2[this.frame] ?? "\u280B");
|
|
4354
|
+
if (isMotionDisabled(this.deps.env)) return;
|
|
4177
4355
|
this.timer = setInterval(() => {
|
|
4178
4356
|
this.frame = (this.frame + 1) % SPINNER_FRAMES2.length;
|
|
4179
4357
|
this.render(SPINNER_FRAMES2[this.frame] ?? "\u280B");
|
|
@@ -4183,6 +4361,7 @@ var TerminalTitle = class _TerminalTitle {
|
|
|
4183
4361
|
// "done" leaves a bell glyph in the title until the next turn starts;
|
|
4184
4362
|
// "idle" (interrupt, unmount) restores the bare title.
|
|
4185
4363
|
stop(kind = "done") {
|
|
4364
|
+
this.running = false;
|
|
4186
4365
|
if (this.timer) {
|
|
4187
4366
|
clearInterval(this.timer);
|
|
4188
4367
|
this.timer = null;
|
|
@@ -4264,7 +4443,7 @@ async function checkForUpdate(currentVersion, fetcher = defaultFetcher2, statePa
|
|
|
4264
4443
|
}
|
|
4265
4444
|
|
|
4266
4445
|
// src/cli/repl.tsx
|
|
4267
|
-
import { jsx as
|
|
4446
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4268
4447
|
var CLEAR_TERMINAL = process.platform === "win32" ? "\x1B[2J\x1B[0f" : "\x1B[2J\x1B[3J\x1B[H";
|
|
4269
4448
|
function emptyUsage() {
|
|
4270
4449
|
return { requests: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
@@ -4311,6 +4490,7 @@ function Repl({
|
|
|
4311
4490
|
const [epoch, setEpoch] = useState7(0);
|
|
4312
4491
|
const [streamingText, setStreamingText] = useState7(null);
|
|
4313
4492
|
const [isStreaming, setIsStreaming] = useState7(false);
|
|
4493
|
+
const [shellRunning, setShellRunning] = useState7(false);
|
|
4314
4494
|
const [thinking, setThinking] = useState7(false);
|
|
4315
4495
|
const [thinkingText, setThinkingText] = useState7("");
|
|
4316
4496
|
const [thoughtSummary, setThoughtSummary] = useState7(null);
|
|
@@ -4325,52 +4505,54 @@ function Repl({
|
|
|
4325
4505
|
const [pendingRewind, setPendingRewindState] = useState7(null);
|
|
4326
4506
|
const [picker, setPickerState] = useState7(null);
|
|
4327
4507
|
const [pendingConnect, setPendingConnectState] = useState7(null);
|
|
4328
|
-
const backendRef =
|
|
4329
|
-
const nextIdRef =
|
|
4330
|
-
const abortRef =
|
|
4331
|
-
const
|
|
4332
|
-
const
|
|
4333
|
-
const
|
|
4334
|
-
const
|
|
4335
|
-
const
|
|
4336
|
-
const
|
|
4337
|
-
const
|
|
4338
|
-
const
|
|
4339
|
-
const
|
|
4340
|
-
const
|
|
4341
|
-
const
|
|
4342
|
-
const
|
|
4343
|
-
const
|
|
4344
|
-
const
|
|
4345
|
-
const
|
|
4346
|
-
const
|
|
4347
|
-
const
|
|
4348
|
-
const
|
|
4349
|
-
const
|
|
4350
|
-
const
|
|
4351
|
-
const
|
|
4352
|
-
const
|
|
4353
|
-
const
|
|
4508
|
+
const backendRef = useRef7(backend);
|
|
4509
|
+
const nextIdRef = useRef7(initialDisplay.length);
|
|
4510
|
+
const abortRef = useRef7(null);
|
|
4511
|
+
const operationKindRef = useRef7(null);
|
|
4512
|
+
const operationIdRef = useRef7(0);
|
|
4513
|
+
const busyCommandRef = useRef7(null);
|
|
4514
|
+
const busyAbortRef = useRef7(null);
|
|
4515
|
+
const streamedRef = useRef7("");
|
|
4516
|
+
const turnStartedAtRef = useRef7(0);
|
|
4517
|
+
const lastCommittedLenRef = useRef7(0);
|
|
4518
|
+
const thinkingRef = useRef7(false);
|
|
4519
|
+
const reasoningRef = useRef7("");
|
|
4520
|
+
const tickerStopRef = useRef7(null);
|
|
4521
|
+
const flushedRef = useRef7({ streamed: "", reasoning: "" });
|
|
4522
|
+
const toolCardsRef = useRef7(/* @__PURE__ */ new Map());
|
|
4523
|
+
const pendingRef = useRef7(null);
|
|
4524
|
+
const permissionChainRef = useRef7(Promise.resolve());
|
|
4525
|
+
const alwaysAllowedRef = useRef7(/* @__PURE__ */ new Set());
|
|
4526
|
+
const alwaysAllowedListRef = useRef7([]);
|
|
4527
|
+
const prevModeRef = useRef7(null);
|
|
4528
|
+
const planApprovalRef = useRef7(false);
|
|
4529
|
+
const pendingRewindRef = useRef7(null);
|
|
4530
|
+
const pickerRef = useRef7(null);
|
|
4531
|
+
const titleRef = useRef7(null);
|
|
4532
|
+
const pendingConnectRef = useRef7(null);
|
|
4533
|
+
const modelNameRef = useRef7(model);
|
|
4534
|
+
const sessionStoreRef = useRef7(sessionStore);
|
|
4535
|
+
const configRef = useRef7(config);
|
|
4354
4536
|
useEffect4(() => {
|
|
4355
4537
|
configRef.current = config;
|
|
4356
4538
|
}, [config]);
|
|
4357
|
-
const usageRef =
|
|
4358
|
-
const turnChunksRef =
|
|
4359
|
-
const messagesRef =
|
|
4539
|
+
const usageRef = useRef7(initialUsage ? { ...initialUsage } : emptyUsage());
|
|
4540
|
+
const turnChunksRef = useRef7(0);
|
|
4541
|
+
const messagesRef = useRef7(initialDisplay);
|
|
4360
4542
|
const [queueItems, setQueueItems] = useState7([]);
|
|
4361
|
-
const queueRef =
|
|
4543
|
+
const queueRef = useRef7(new PromptQueue());
|
|
4362
4544
|
const [pendingImages, setPendingImages] = useState7([]);
|
|
4363
|
-
const pendingImagesRef =
|
|
4364
|
-
const imageSeqRef =
|
|
4545
|
+
const pendingImagesRef = useRef7([]);
|
|
4546
|
+
const imageSeqRef = useRef7(1);
|
|
4365
4547
|
const [inputRefill, setInputRefill] = useState7();
|
|
4366
|
-
const refillSeqRef =
|
|
4367
|
-
const lastEscRef =
|
|
4548
|
+
const refillSeqRef = useRef7(0);
|
|
4549
|
+
const lastEscRef = useRef7(null);
|
|
4368
4550
|
const [gitBranch, setGitBranch] = useState7(null);
|
|
4369
4551
|
const [contextUsage, setContextUsage] = useState7(null);
|
|
4370
4552
|
const [initialHistory] = useState7(() => loadHistory());
|
|
4371
|
-
const budgetWarnedRef =
|
|
4372
|
-
const budgetExceededRef =
|
|
4373
|
-
const runStreamRef =
|
|
4553
|
+
const budgetWarnedRef = useRef7(false);
|
|
4554
|
+
const budgetExceededRef = useRef7(false);
|
|
4555
|
+
const runStreamRef = useRef7(
|
|
4374
4556
|
null
|
|
4375
4557
|
);
|
|
4376
4558
|
const pushMessage = useCallback(
|
|
@@ -4463,13 +4645,40 @@ function Repl({
|
|
|
4463
4645
|
pendingImagesRef.current = [];
|
|
4464
4646
|
setPendingImages([]);
|
|
4465
4647
|
}, []);
|
|
4466
|
-
const
|
|
4467
|
-
() =>
|
|
4468
|
-
usageRef.current
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4648
|
+
const recordUsage = useCallback(
|
|
4649
|
+
(delta, _modelName, costUsd) => {
|
|
4650
|
+
const usage = usageRef.current;
|
|
4651
|
+
usage.requests += 1;
|
|
4652
|
+
usage.promptTokens += delta.promptTokens;
|
|
4653
|
+
usage.completionTokens += delta.completionTokens;
|
|
4654
|
+
usage.totalTokens += delta.promptTokens + delta.completionTokens || delta.totalTokens;
|
|
4655
|
+
if (delta.cachedPromptTokens) {
|
|
4656
|
+
usage.cachedPromptTokens = (usage.cachedPromptTokens ?? 0) + delta.cachedPromptTokens;
|
|
4657
|
+
}
|
|
4658
|
+
if (delta.cacheReadInputTokens) {
|
|
4659
|
+
usage.cacheReadInputTokens = (usage.cacheReadInputTokens ?? 0) + delta.cacheReadInputTokens;
|
|
4660
|
+
}
|
|
4661
|
+
if (costUsd === null) usage.costUnknown = true;
|
|
4662
|
+
else usage.costUsd = (usage.costUsd ?? 0) + costUsd;
|
|
4663
|
+
if (cacheUsageReported(delta)) {
|
|
4664
|
+
if (!usage.cache) usage.cache = { cachedTokens: 0, promptTokens: 0 };
|
|
4665
|
+
addToCacheTotals(usage.cache, delta);
|
|
4666
|
+
}
|
|
4667
|
+
setUsageVersion((version) => version + 1);
|
|
4668
|
+
},
|
|
4669
|
+
[]
|
|
4472
4670
|
);
|
|
4671
|
+
useEffect4(() => {
|
|
4672
|
+
if (backendRef.current instanceof AgentLoop) backendRef.current.onUsage = recordUsage;
|
|
4673
|
+
}, [recordUsage]);
|
|
4674
|
+
const sessionCostUsd = useCallback(() => {
|
|
4675
|
+
const usage = usageRef.current;
|
|
4676
|
+
if (usage.costUsd !== void 0 || usage.costUnknown) return usage.costUsd ?? 0;
|
|
4677
|
+
return computeCostUsd(
|
|
4678
|
+
usage,
|
|
4679
|
+
config.models.find((model2) => model2.name === modelNameRef.current)
|
|
4680
|
+
);
|
|
4681
|
+
}, [config]);
|
|
4473
4682
|
const handlePasteImage = useCallback(() => {
|
|
4474
4683
|
void readClipboardImage().then((result) => {
|
|
4475
4684
|
if (!result) return;
|
|
@@ -4657,10 +4866,19 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
4657
4866
|
const stopped = stopBackgroundWork();
|
|
4658
4867
|
if (stopped > 0) {
|
|
4659
4868
|
pushMessage("system", `${stopped} background task(s) still running; reports will be lost.`);
|
|
4660
|
-
setTimeout(exit, 50);
|
|
4661
|
-
return;
|
|
4662
4869
|
}
|
|
4663
|
-
|
|
4870
|
+
void (async () => {
|
|
4871
|
+
try {
|
|
4872
|
+
await sessionStoreRef.current?.close();
|
|
4873
|
+
} catch (error) {
|
|
4874
|
+
pushMessage(
|
|
4875
|
+
"system",
|
|
4876
|
+
`Failed to flush session metadata: ${error instanceof Error ? error.message : String(error)}`
|
|
4877
|
+
);
|
|
4878
|
+
}
|
|
4879
|
+
if (stopped > 0) setTimeout(exit, 50);
|
|
4880
|
+
else exit();
|
|
4881
|
+
})();
|
|
4664
4882
|
}, [exit, pushMessage, stopBackgroundWork]);
|
|
4665
4883
|
useInput((_input, key) => {
|
|
4666
4884
|
if (key.escape) {
|
|
@@ -4734,6 +4952,7 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
4734
4952
|
system: SYSTEM_PROMPT,
|
|
4735
4953
|
sessionStore: sessionStoreRef.current,
|
|
4736
4954
|
contextMaxTokens: contextWindowTokens(config, name),
|
|
4955
|
+
maxTokens: modelConfig.maxTokens,
|
|
4737
4956
|
providerMetadata: reasoningEffortMetadata(config, name),
|
|
4738
4957
|
temperature: modelConfig.temperature,
|
|
4739
4958
|
streamIdleTimeoutSec: modelConfig.streamIdleTimeoutSec,
|
|
@@ -4746,6 +4965,7 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
4746
4965
|
await loop.loadMessages([...prev.getStarMessages()]);
|
|
4747
4966
|
}
|
|
4748
4967
|
attachConfirmHandler(loop);
|
|
4968
|
+
loop.onUsage = recordUsage;
|
|
4749
4969
|
backendRef.current = loop;
|
|
4750
4970
|
modelNameRef.current = name;
|
|
4751
4971
|
setModelName(name);
|
|
@@ -4755,7 +4975,7 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
4755
4975
|
return `Failed to switch model: ${error instanceof Error ? error.message : String(error)}`;
|
|
4756
4976
|
}
|
|
4757
4977
|
},
|
|
4758
|
-
[config, cwd, attachConfirmHandler]
|
|
4978
|
+
[config, cwd, attachConfirmHandler, recordUsage]
|
|
4759
4979
|
);
|
|
4760
4980
|
const pickReasoningEffort = useCallback(
|
|
4761
4981
|
async (name) => {
|
|
@@ -4812,38 +5032,76 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
4812
5032
|
if (!resolvedId) {
|
|
4813
5033
|
return `Session not found: ${id}`;
|
|
4814
5034
|
}
|
|
4815
|
-
const
|
|
5035
|
+
const currentStore = sessionStoreRef.current;
|
|
5036
|
+
const sameStore = currentStore?.id === resolvedId ? currentStore : null;
|
|
5037
|
+
const resumed = sameStore ? await (async () => {
|
|
5038
|
+
const [meta, loaded] = await Promise.all([sameStore.meta(), sameStore.starMessages()]);
|
|
5039
|
+
return { meta, messages: reconcileStarMessages(loaded) };
|
|
5040
|
+
})() : await resumeSession(resolvedId);
|
|
4816
5041
|
if (!resumed) {
|
|
4817
5042
|
return `Session not found: ${id}`;
|
|
4818
5043
|
}
|
|
4819
|
-
const store = await SessionStore.open(resolvedId);
|
|
5044
|
+
const store = sameStore ?? await SessionStore.open(resolvedId);
|
|
4820
5045
|
if (!store) {
|
|
4821
5046
|
return `Session not found: ${id}`;
|
|
4822
5047
|
}
|
|
4823
5048
|
const stopped = stopBackgroundWork();
|
|
4824
5049
|
await current.loadMessages(resumed.messages);
|
|
5050
|
+
const previousStore = sessionStoreRef.current;
|
|
4825
5051
|
current.setSessionStore(store);
|
|
4826
5052
|
sessionStoreRef.current = store;
|
|
5053
|
+
if (previousStore !== store) await previousStore?.close();
|
|
4827
5054
|
setTodoSession(store.id);
|
|
4828
5055
|
setTodos(await loadTodos(cwd, store.id));
|
|
4829
5056
|
hydrateSnapshots(await loadSessionSnapshots(store.dir));
|
|
4830
5057
|
const display = buildDisplayMessages(resumed.messages);
|
|
4831
5058
|
nextIdRef.current = display.length;
|
|
4832
|
-
|
|
5059
|
+
redrawMessages(display);
|
|
4833
5060
|
usageRef.current = resumed.meta.usage ? { ...resumed.meta.usage } : emptyUsage();
|
|
5061
|
+
budgetWarnedRef.current = false;
|
|
5062
|
+
budgetExceededRef.current = false;
|
|
4834
5063
|
setUsageVersion((v) => v + 1);
|
|
4835
5064
|
const stoppedNote = stopped > 0 ? ` Stopped ${stopped} background task(s) from the previous session.` : "";
|
|
4836
5065
|
return `Resumed session ${resolvedId} (${resumed.messages.length} messages).${stoppedNote}`;
|
|
4837
5066
|
},
|
|
4838
|
-
[
|
|
5067
|
+
[cwd, redrawMessages, stopBackgroundWork]
|
|
4839
5068
|
);
|
|
4840
5069
|
const terminalTitle = useCallback(() => {
|
|
4841
5070
|
titleRef.current ??= TerminalTitle.forCwd(cwd);
|
|
4842
5071
|
return titleRef.current;
|
|
4843
5072
|
}, [cwd]);
|
|
5073
|
+
const drainQueue = useCallback(() => {
|
|
5074
|
+
if (abortRef.current || busyCommandRef.current || planApprovalRef.current) return;
|
|
5075
|
+
const status = budgetState(sessionCostUsd(), config.sessionBudgetUsd);
|
|
5076
|
+
const nextPrompt = queueRef.current.dequeue();
|
|
5077
|
+
if (!nextPrompt) return;
|
|
5078
|
+
if (status === "exceeded") {
|
|
5079
|
+
const dropped = queueRef.current.clear().length + 1;
|
|
5080
|
+
setQueueItems([]);
|
|
5081
|
+
pushMessage("system", `Dropped ${dropped} queued message(s) \u2014 session budget exceeded.`);
|
|
5082
|
+
} else {
|
|
5083
|
+
setQueueItems(queueRef.current.list());
|
|
5084
|
+
void runStreamRef.current?.(nextPrompt.text, nextPrompt.images);
|
|
5085
|
+
}
|
|
5086
|
+
}, [config.sessionBudgetUsd, pushMessage, sessionCostUsd]);
|
|
4844
5087
|
const runStream = useCallback(
|
|
4845
5088
|
async (input, extraImages = []) => {
|
|
4846
|
-
const
|
|
5089
|
+
const controller = new AbortController();
|
|
5090
|
+
const operationId = ++operationIdRef.current;
|
|
5091
|
+
abortRef.current = controller;
|
|
5092
|
+
operationKindRef.current = "model";
|
|
5093
|
+
let resolved;
|
|
5094
|
+
try {
|
|
5095
|
+
resolved = await resolveMentions(input, cwd);
|
|
5096
|
+
} catch (error) {
|
|
5097
|
+
if (operationIdRef.current === operationId) {
|
|
5098
|
+
abortRef.current = null;
|
|
5099
|
+
operationKindRef.current = null;
|
|
5100
|
+
}
|
|
5101
|
+
pushMessage("system", `Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
5102
|
+
drainQueue();
|
|
5103
|
+
return;
|
|
5104
|
+
}
|
|
4847
5105
|
const images = [...resolved.images, ...extraImages];
|
|
4848
5106
|
pushMessage(
|
|
4849
5107
|
"user",
|
|
@@ -4853,8 +5111,6 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
4853
5111
|
for (const skip of resolved.skipped) {
|
|
4854
5112
|
pushMessage("system", `Skipped @${skip.path}: ${skip.reason}`);
|
|
4855
5113
|
}
|
|
4856
|
-
const controller = new AbortController();
|
|
4857
|
-
abortRef.current = controller;
|
|
4858
5114
|
const turnStartedAt = Date.now();
|
|
4859
5115
|
turnStartedAtRef.current = turnStartedAt;
|
|
4860
5116
|
streamedRef.current = "";
|
|
@@ -4967,27 +5223,6 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
4967
5223
|
reasoningRef.current = "";
|
|
4968
5224
|
setThinking(true);
|
|
4969
5225
|
setThinkingText("");
|
|
4970
|
-
} else if (event.type === "finish") {
|
|
4971
|
-
if (event.usage) {
|
|
4972
|
-
const usage = usageRef.current;
|
|
4973
|
-
usage.requests += 1;
|
|
4974
|
-
usage.promptTokens += event.usage.promptTokens;
|
|
4975
|
-
usage.completionTokens += event.usage.completionTokens;
|
|
4976
|
-
usage.totalTokens += event.usage.totalTokens;
|
|
4977
|
-
if (event.usage.cachedPromptTokens) {
|
|
4978
|
-
usage.cachedPromptTokens = (usage.cachedPromptTokens ?? 0) + event.usage.cachedPromptTokens;
|
|
4979
|
-
}
|
|
4980
|
-
if (event.usage.cacheReadInputTokens) {
|
|
4981
|
-
usage.cacheReadInputTokens = (usage.cacheReadInputTokens ?? 0) + event.usage.cacheReadInputTokens;
|
|
4982
|
-
}
|
|
4983
|
-
if (cacheUsageReported(event.usage)) {
|
|
4984
|
-
if (!usage.cache) usage.cache = { cachedTokens: 0, promptTokens: 0 };
|
|
4985
|
-
addToCacheTotals(usage.cache, event.usage);
|
|
4986
|
-
}
|
|
4987
|
-
setUsageVersion((v) => v + 1);
|
|
4988
|
-
sessionStoreRef.current?.addUsage(event.usage).catch(() => {
|
|
4989
|
-
});
|
|
4990
|
-
}
|
|
4991
5226
|
} else if (event.type === "retry") {
|
|
4992
5227
|
streamedRef.current = streamedRef.current.slice(0, lastCommittedLenRef.current);
|
|
4993
5228
|
flushedRef.current = { ...flushedRef.current, streamed: "" };
|
|
@@ -5014,7 +5249,10 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
5014
5249
|
} finally {
|
|
5015
5250
|
tickerStopRef.current?.();
|
|
5016
5251
|
tickerStopRef.current = null;
|
|
5017
|
-
|
|
5252
|
+
if (operationIdRef.current === operationId) {
|
|
5253
|
+
abortRef.current = null;
|
|
5254
|
+
operationKindRef.current = null;
|
|
5255
|
+
}
|
|
5018
5256
|
setIsStreaming(false);
|
|
5019
5257
|
thinkingRef.current = false;
|
|
5020
5258
|
reasoningRef.current = "";
|
|
@@ -5073,25 +5311,18 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
5073
5311
|
const backend2 = backendRef.current;
|
|
5074
5312
|
if (backend2 instanceof AgentLoop) {
|
|
5075
5313
|
const undelivered = backend2.takeUndeliveredSteers();
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
}
|
|
5081
|
-
const nextPrompt = queueRef.current.dequeue();
|
|
5082
|
-
if (nextPrompt) {
|
|
5083
|
-
if (status === "exceeded") {
|
|
5084
|
-
const dropped = queueRef.current.clear().length + 1;
|
|
5085
|
-
setQueueItems([]);
|
|
5086
|
-
pushMessage(
|
|
5087
|
-
"system",
|
|
5088
|
-
`Dropped ${dropped} queued message(s) \u2014 session budget exceeded.`
|
|
5089
|
-
);
|
|
5314
|
+
if (interrupted && undelivered.length > 0) {
|
|
5315
|
+
refillSeqRef.current += 1;
|
|
5316
|
+
setInputRefill({ text: undelivered.join("\n"), seq: refillSeqRef.current });
|
|
5317
|
+
pushMessage("system", "Undelivered steering input restored to the draft.");
|
|
5090
5318
|
} else {
|
|
5091
|
-
|
|
5092
|
-
|
|
5319
|
+
for (const text of [...undelivered].reverse()) {
|
|
5320
|
+
queueRef.current.enqueueFront({ text, images: [] });
|
|
5321
|
+
}
|
|
5322
|
+
if (undelivered.length > 0) setQueueItems(queueRef.current.list());
|
|
5093
5323
|
}
|
|
5094
5324
|
}
|
|
5325
|
+
drainQueue();
|
|
5095
5326
|
}
|
|
5096
5327
|
},
|
|
5097
5328
|
[
|
|
@@ -5102,7 +5333,8 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
5102
5333
|
cwd,
|
|
5103
5334
|
config,
|
|
5104
5335
|
sessionCostUsd,
|
|
5105
|
-
terminalTitle
|
|
5336
|
+
terminalTitle,
|
|
5337
|
+
drainQueue
|
|
5106
5338
|
]
|
|
5107
5339
|
);
|
|
5108
5340
|
useEffect4(() => {
|
|
@@ -5111,13 +5343,17 @@ Full configuration details: ${CONFIG_DOCS_URL}`;
|
|
|
5111
5343
|
const handlePlanDecision = useCallback(
|
|
5112
5344
|
(decision) => {
|
|
5113
5345
|
setPlanApproval(false);
|
|
5114
|
-
if (decision === "no")
|
|
5346
|
+
if (decision === "no") {
|
|
5347
|
+
drainQueue();
|
|
5348
|
+
return;
|
|
5349
|
+
}
|
|
5350
|
+
if (abortRef.current || busyCommandRef.current) return;
|
|
5115
5351
|
const restore = prevModeRef.current ?? "ask";
|
|
5116
5352
|
applySessionMode(restore);
|
|
5117
5353
|
pushMessage("system", `Plan approved \u2014 permission mode restored to ${restore}.`);
|
|
5118
5354
|
void runStream("The plan above is approved. Proceed with the implementation.");
|
|
5119
5355
|
},
|
|
5120
|
-
[applySessionMode, pushMessage, runStream, setPlanApproval]
|
|
5356
|
+
[applySessionMode, drainQueue, pushMessage, runStream, setPlanApproval]
|
|
5121
5357
|
);
|
|
5122
5358
|
const registry = useMemo(() => {
|
|
5123
5359
|
const ctx = {
|
|
@@ -5298,12 +5534,16 @@ ${lines.join("\n")}`;
|
|
|
5298
5534
|
return "Nothing to fork \u2014 the current session has no messages yet.";
|
|
5299
5535
|
}
|
|
5300
5536
|
const fork = await SessionStore.create(cwd, modelNameRef.current);
|
|
5301
|
-
await fork.replaceMessages(messages2)
|
|
5537
|
+
if (!await fork.replaceMessages(messages2)) {
|
|
5538
|
+
await fork.close();
|
|
5539
|
+
return "Failed to persist the fork; the current session is unchanged.";
|
|
5540
|
+
}
|
|
5302
5541
|
const oldMeta = await store.meta();
|
|
5303
5542
|
const title = `Fork of ${oldMeta.title || shortSessionId(oldMeta.id)}`;
|
|
5304
5543
|
await fork.setTitle(title);
|
|
5305
5544
|
current.setSessionStore(fork);
|
|
5306
5545
|
sessionStoreRef.current = fork;
|
|
5546
|
+
await store.close();
|
|
5307
5547
|
setTodoSession(fork.id);
|
|
5308
5548
|
return `Forked into new session ${fork.id} ("${title}") and switched to it. Checkpoints and rewind history do not carry over.`;
|
|
5309
5549
|
},
|
|
@@ -5314,8 +5554,10 @@ ${lines.join("\n")}`;
|
|
|
5314
5554
|
}
|
|
5315
5555
|
const stopped = stopBackgroundWork();
|
|
5316
5556
|
const store = await SessionStore.create(cwd, modelNameRef.current);
|
|
5557
|
+
const previousStore = sessionStoreRef.current;
|
|
5317
5558
|
current.setSessionStore(store);
|
|
5318
5559
|
sessionStoreRef.current = store;
|
|
5560
|
+
await previousStore?.close();
|
|
5319
5561
|
setTodoSession(store.id);
|
|
5320
5562
|
const [first] = current.getStarMessages();
|
|
5321
5563
|
await current.loadMessages(first?.message.role === "system" ? [first] : []);
|
|
@@ -5353,9 +5595,11 @@ ${lines.join("\n")}`;
|
|
|
5353
5595
|
},
|
|
5354
5596
|
listTasks: () => formatTaskList(defaultTaskManager.list()),
|
|
5355
5597
|
showUsage: () => {
|
|
5598
|
+
const usage = usageRef.current;
|
|
5356
5599
|
const modelConfig = config.models.find((m) => m.name === modelNameRef.current);
|
|
5357
|
-
|
|
5358
|
-
|
|
5600
|
+
const cost = usage.costUsd !== void 0 || usage.costUnknown ? `Estimated cost: $${formatDollars(usage.costUsd ?? 0)} across recorded models${usage.costUnknown ? " (some requests have no configured price)" : ""}` : estimateCost(usage, modelNameRef.current, modelConfig);
|
|
5601
|
+
return `${formatUsage(usage)}
|
|
5602
|
+
${cost}`;
|
|
5359
5603
|
},
|
|
5360
5604
|
showGlobalUsage: async () => formatUsageDashboard(await collectUsageStats(), config.models),
|
|
5361
5605
|
compactContext: async () => {
|
|
@@ -5363,6 +5607,8 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
|
|
|
5363
5607
|
return `Busy \u2014 /${busyCommandRef.current} is still running.`;
|
|
5364
5608
|
}
|
|
5365
5609
|
busyCommandRef.current = "compact";
|
|
5610
|
+
const operationId = ++operationIdRef.current;
|
|
5611
|
+
operationKindRef.current = "command";
|
|
5366
5612
|
const controller = new AbortController();
|
|
5367
5613
|
busyAbortRef.current = controller;
|
|
5368
5614
|
turnStartedAtRef.current = Date.now();
|
|
@@ -5377,20 +5623,19 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
|
|
|
5377
5623
|
if (result.compacted && result.messages) {
|
|
5378
5624
|
const display = buildDisplayMessages(result.messages);
|
|
5379
5625
|
nextIdRef.current = display.length;
|
|
5380
|
-
|
|
5626
|
+
redrawMessages(display);
|
|
5381
5627
|
}
|
|
5382
5628
|
return result.message;
|
|
5383
5629
|
} finally {
|
|
5384
5630
|
tickerStopRef.current?.();
|
|
5385
5631
|
tickerStopRef.current = null;
|
|
5386
5632
|
setActivity(null);
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
setQueueItems(queueRef.current.list());
|
|
5392
|
-
void runStreamRef.current?.(nextPrompt.text, nextPrompt.images);
|
|
5633
|
+
if (operationIdRef.current === operationId) {
|
|
5634
|
+
busyCommandRef.current = null;
|
|
5635
|
+
busyAbortRef.current = null;
|
|
5636
|
+
operationKindRef.current = null;
|
|
5393
5637
|
}
|
|
5638
|
+
drainQueue();
|
|
5394
5639
|
}
|
|
5395
5640
|
},
|
|
5396
5641
|
exportSession: (arg) => exportSession({
|
|
@@ -5592,7 +5837,6 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5592
5837
|
pickReasoningEffort,
|
|
5593
5838
|
resume,
|
|
5594
5839
|
runStream,
|
|
5595
|
-
applyMessages,
|
|
5596
5840
|
redrawMessages,
|
|
5597
5841
|
applySessionMode,
|
|
5598
5842
|
setPendingRewind,
|
|
@@ -5600,13 +5844,17 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5600
5844
|
showPicker,
|
|
5601
5845
|
handlePickerCancel,
|
|
5602
5846
|
setPicker,
|
|
5603
|
-
stopBackgroundWork
|
|
5847
|
+
stopBackgroundWork,
|
|
5848
|
+
drainQueue
|
|
5604
5849
|
]);
|
|
5605
5850
|
const runShellBang = useCallback(
|
|
5606
5851
|
async (raw) => {
|
|
5607
5852
|
pushMessage("user", `!${raw.trim()}`);
|
|
5608
5853
|
const controller = new AbortController();
|
|
5854
|
+
const operationId = ++operationIdRef.current;
|
|
5609
5855
|
abortRef.current = controller;
|
|
5856
|
+
operationKindRef.current = "shell";
|
|
5857
|
+
setShellRunning(true);
|
|
5610
5858
|
try {
|
|
5611
5859
|
const outcome = await executeShellBang(raw, cwd, controller.signal);
|
|
5612
5860
|
if (!outcome.ok) {
|
|
@@ -5630,13 +5878,18 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5630
5878
|
if (current instanceof AgentLoop) {
|
|
5631
5879
|
await current.appendContextMessage(outcome.contextMessage);
|
|
5632
5880
|
}
|
|
5881
|
+
} catch (error) {
|
|
5882
|
+
pushMessage("system", `Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
5633
5883
|
} finally {
|
|
5634
|
-
if (
|
|
5884
|
+
if (operationIdRef.current === operationId) {
|
|
5635
5885
|
abortRef.current = null;
|
|
5886
|
+
operationKindRef.current = null;
|
|
5636
5887
|
}
|
|
5888
|
+
setShellRunning(false);
|
|
5889
|
+
drainQueue();
|
|
5637
5890
|
}
|
|
5638
5891
|
},
|
|
5639
|
-
[cwd, pushMessage]
|
|
5892
|
+
[cwd, drainQueue, pushMessage]
|
|
5640
5893
|
);
|
|
5641
5894
|
const handleSubmit = useCallback(
|
|
5642
5895
|
(text) => {
|
|
@@ -5675,7 +5928,28 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5675
5928
|
return;
|
|
5676
5929
|
}
|
|
5677
5930
|
}
|
|
5678
|
-
|
|
5931
|
+
const lifecycleCommand = BUSY_BLOCKED_COMMANDS.has(parsed.name) && parsed.name !== "compact";
|
|
5932
|
+
const operationId = ++operationIdRef.current;
|
|
5933
|
+
if (lifecycleCommand) {
|
|
5934
|
+
busyCommandRef.current = parsed.name;
|
|
5935
|
+
operationKindRef.current = "command";
|
|
5936
|
+
}
|
|
5937
|
+
void (async () => {
|
|
5938
|
+
try {
|
|
5939
|
+
await command.run(parsed.args, registry.ctx);
|
|
5940
|
+
} catch (error) {
|
|
5941
|
+
pushMessage(
|
|
5942
|
+
"system",
|
|
5943
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
5944
|
+
);
|
|
5945
|
+
} finally {
|
|
5946
|
+
if (lifecycleCommand && operationIdRef.current === operationId) {
|
|
5947
|
+
busyCommandRef.current = null;
|
|
5948
|
+
operationKindRef.current = null;
|
|
5949
|
+
drainQueue();
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
})();
|
|
5679
5953
|
return;
|
|
5680
5954
|
}
|
|
5681
5955
|
const budget = config.sessionBudgetUsd;
|
|
@@ -5690,7 +5964,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5690
5964
|
if (images.length > 0) clearPendingImages();
|
|
5691
5965
|
if (abortRef.current || busyCommandRef.current) {
|
|
5692
5966
|
const backend2 = backendRef.current;
|
|
5693
|
-
if (abortRef.current && images.length === 0 && backend2 instanceof AgentLoop) {
|
|
5967
|
+
if (abortRef.current && operationKindRef.current === "model" && images.length === 0 && backend2 instanceof AgentLoop) {
|
|
5968
|
+
const requestedOperation = operationIdRef.current;
|
|
5694
5969
|
void (async () => {
|
|
5695
5970
|
const picked = await showPicker("Turn is running \u2014 how should this be sent?", [
|
|
5696
5971
|
{
|
|
@@ -5704,6 +5979,15 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5704
5979
|
description: "sent as a new prompt once the current turn finishes"
|
|
5705
5980
|
}
|
|
5706
5981
|
]);
|
|
5982
|
+
if (operationIdRef.current !== requestedOperation || !abortRef.current) {
|
|
5983
|
+
if (!abortRef.current && !busyCommandRef.current) {
|
|
5984
|
+
void runStream(text, images);
|
|
5985
|
+
} else {
|
|
5986
|
+
queueRef.current.enqueue({ text, images });
|
|
5987
|
+
setQueueItems(queueRef.current.list());
|
|
5988
|
+
}
|
|
5989
|
+
return;
|
|
5990
|
+
}
|
|
5707
5991
|
if (picked === "steer") {
|
|
5708
5992
|
backend2.injectUserMessage(text);
|
|
5709
5993
|
pushMessage(
|
|
@@ -5736,7 +6020,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5736
6020
|
config,
|
|
5737
6021
|
clearPendingImages,
|
|
5738
6022
|
sessionCostUsd,
|
|
5739
|
-
showPicker
|
|
6023
|
+
showPicker,
|
|
6024
|
+
drainQueue
|
|
5740
6025
|
]
|
|
5741
6026
|
);
|
|
5742
6027
|
const commandHints = useMemo(
|
|
@@ -5749,8 +6034,11 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5749
6034
|
[registry]
|
|
5750
6035
|
);
|
|
5751
6036
|
useEffect4(() => {
|
|
6037
|
+
let cancelled = false;
|
|
5752
6038
|
const refresh = () => {
|
|
5753
|
-
|
|
6039
|
+
void getGitSummaryCachedAsync(cwd).then((summary) => {
|
|
6040
|
+
if (!cancelled) setGitBranch(summary?.branch ?? null);
|
|
6041
|
+
});
|
|
5754
6042
|
const current = backendRef.current;
|
|
5755
6043
|
if (current instanceof AgentLoop) {
|
|
5756
6044
|
const window_ = contextWindowTokens(config, modelNameRef.current);
|
|
@@ -5766,14 +6054,16 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5766
6054
|
}
|
|
5767
6055
|
};
|
|
5768
6056
|
refresh();
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
6057
|
+
const timer = isStreaming ? setInterval(refresh, 2e3) : null;
|
|
6058
|
+
return () => {
|
|
6059
|
+
cancelled = true;
|
|
6060
|
+
if (timer) clearInterval(timer);
|
|
6061
|
+
};
|
|
5772
6062
|
}, [cwd, config, isStreaming, epoch, modelName]);
|
|
5773
6063
|
const sessionCost = sessionCostUsd();
|
|
5774
|
-
return /* @__PURE__ */
|
|
5775
|
-
/* @__PURE__ */
|
|
5776
|
-
(thinking || activity !== null) && /* @__PURE__ */
|
|
6064
|
+
return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
6065
|
+
/* @__PURE__ */ jsx12(MessageList, { messages }, `messages-${epoch}`),
|
|
6066
|
+
(thinking || activity !== null) && /* @__PURE__ */ jsx12(
|
|
5777
6067
|
ThinkingIndicator,
|
|
5778
6068
|
{
|
|
5779
6069
|
reasoning: thinkingText,
|
|
@@ -5781,9 +6071,9 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5781
6071
|
startedAt: turnStartedAtRef.current
|
|
5782
6072
|
}
|
|
5783
6073
|
),
|
|
5784
|
-
!thinking && activity === null && thoughtSummary !== null && /* @__PURE__ */
|
|
5785
|
-
streamingText !== null && (streamingText !== "" || turnChunksRef.current === 0) && /* @__PURE__ */
|
|
5786
|
-
pending && /* @__PURE__ */
|
|
6074
|
+
!thinking && activity === null && thoughtSummary !== null && /* @__PURE__ */ jsx12(Text14, { dimColor: true, children: thoughtSummary }),
|
|
6075
|
+
streamingText !== null && (streamingText !== "" || turnChunksRef.current === 0) && /* @__PURE__ */ jsx12(StreamingMessage, { text: streamingText, continuation: turnChunksRef.current > 0 }),
|
|
6076
|
+
pending && /* @__PURE__ */ jsx12(
|
|
5787
6077
|
PermissionPrompt,
|
|
5788
6078
|
{
|
|
5789
6079
|
request: pending.request,
|
|
@@ -5791,8 +6081,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5791
6081
|
onDecision: handleDecision
|
|
5792
6082
|
}
|
|
5793
6083
|
),
|
|
5794
|
-
planApproval && /* @__PURE__ */
|
|
5795
|
-
pendingRewind && /* @__PURE__ */
|
|
6084
|
+
planApproval && /* @__PURE__ */ jsx12(ExitPlanPrompt, { onDecision: handlePlanDecision }),
|
|
6085
|
+
pendingRewind && /* @__PURE__ */ jsx12(
|
|
5796
6086
|
RewindConfirmPrompt,
|
|
5797
6087
|
{
|
|
5798
6088
|
summary: pendingRewind.summary,
|
|
@@ -5802,7 +6092,7 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5802
6092
|
onDecision: handleRewindDecision
|
|
5803
6093
|
}
|
|
5804
6094
|
),
|
|
5805
|
-
picker && /* @__PURE__ */
|
|
6095
|
+
picker && /* @__PURE__ */ jsx12(
|
|
5806
6096
|
SelectPrompt,
|
|
5807
6097
|
{
|
|
5808
6098
|
title: picker.title,
|
|
@@ -5812,7 +6102,7 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5812
6102
|
onDelete: picker.onDelete
|
|
5813
6103
|
}
|
|
5814
6104
|
),
|
|
5815
|
-
pendingConnect && /* @__PURE__ */
|
|
6105
|
+
pendingConnect && /* @__PURE__ */ jsx12(
|
|
5816
6106
|
ConnectWizard,
|
|
5817
6107
|
{
|
|
5818
6108
|
existingProviderNames: config.providers.map((p) => p.name),
|
|
@@ -5824,25 +6114,25 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5824
6114
|
onCancel: cancelConnect
|
|
5825
6115
|
}
|
|
5826
6116
|
),
|
|
5827
|
-
queueItems.slice(0, MAX_VISIBLE_QUEUED).map((item) => /* @__PURE__ */
|
|
6117
|
+
queueItems.slice(0, MAX_VISIBLE_QUEUED).map((item) => /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
|
|
5828
6118
|
"queued: ",
|
|
5829
6119
|
item.text.length > 80 ? `${item.text.slice(0, 80)}\u2026` : item.text
|
|
5830
6120
|
] }, item.id)),
|
|
5831
|
-
queueItems.length > MAX_VISIBLE_QUEUED && /* @__PURE__ */
|
|
6121
|
+
queueItems.length > MAX_VISIBLE_QUEUED && /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
|
|
5832
6122
|
"\u2026 ",
|
|
5833
6123
|
queueItems.length - MAX_VISIBLE_QUEUED,
|
|
5834
6124
|
" more queued"
|
|
5835
6125
|
] }),
|
|
5836
|
-
pendingImages.map((staged) => /* @__PURE__ */
|
|
6126
|
+
pendingImages.map((staged) => /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
|
|
5837
6127
|
"[image attached: ",
|
|
5838
6128
|
staged.image.path,
|
|
5839
6129
|
"]"
|
|
5840
6130
|
] }, staged.id)),
|
|
5841
|
-
/* @__PURE__ */
|
|
5842
|
-
/* @__PURE__ */
|
|
6131
|
+
/* @__PURE__ */ jsx12(TodoPanel, { todos }),
|
|
6132
|
+
/* @__PURE__ */ jsx12(
|
|
5843
6133
|
InputBox,
|
|
5844
6134
|
{
|
|
5845
|
-
isStreaming,
|
|
6135
|
+
isStreaming: isStreaming || shellRunning,
|
|
5846
6136
|
disabled: pending !== null || planApproval || pendingRewind !== null || pendingConnect !== null || picker !== null,
|
|
5847
6137
|
commands: commandHints,
|
|
5848
6138
|
cwd,
|
|
@@ -5854,7 +6144,7 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5854
6144
|
onPasteImage: handlePasteImage
|
|
5855
6145
|
}
|
|
5856
6146
|
),
|
|
5857
|
-
/* @__PURE__ */
|
|
6147
|
+
/* @__PURE__ */ jsx12(
|
|
5858
6148
|
StatusBar,
|
|
5859
6149
|
{
|
|
5860
6150
|
cwd,
|
|
@@ -5877,7 +6167,7 @@ function renderRepl(backend, opts) {
|
|
|
5877
6167
|
defaultTaskManager.cleanup();
|
|
5878
6168
|
});
|
|
5879
6169
|
return render(
|
|
5880
|
-
/* @__PURE__ */
|
|
6170
|
+
/* @__PURE__ */ jsx12(
|
|
5881
6171
|
Repl,
|
|
5882
6172
|
{
|
|
5883
6173
|
backend,
|
|
@@ -5899,4 +6189,4 @@ export {
|
|
|
5899
6189
|
Repl,
|
|
5900
6190
|
renderRepl
|
|
5901
6191
|
};
|
|
5902
|
-
//# sourceMappingURL=repl-
|
|
6192
|
+
//# sourceMappingURL=repl-N2H7JDSH.js.map
|