@spinalfluid/ultrac 0.1.11 → 0.1.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 +15 -9
- package/dist/cli.js +810 -256
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -399,6 +399,7 @@ function modePermissionAccessError(mode, toolName, permission) {
|
|
|
399
399
|
var EchoProvider = class {
|
|
400
400
|
name = "echo";
|
|
401
401
|
async complete(request) {
|
|
402
|
+
if (request.messages.some((message) => message.image)) throw Object.assign(new Error("Echo provider cannot inspect image attachments."), { status: 400 });
|
|
402
403
|
const lastUserMessage = [...request.messages].reverse().find((message) => message.role === "user");
|
|
403
404
|
return {
|
|
404
405
|
text: JSON.stringify(
|
|
@@ -1136,6 +1137,9 @@ function pinCurrentTurn(units, isUser) {
|
|
|
1136
1137
|
}
|
|
1137
1138
|
}
|
|
1138
1139
|
function messageChars(message) {
|
|
1140
|
+
if (message && typeof message === "object" && "image" in message && message.image) {
|
|
1141
|
+
return JSON.stringify({ ...message, image: void 0 }).length + 4096 * CHARS_PER_TOKEN;
|
|
1142
|
+
}
|
|
1139
1143
|
return JSON.stringify(message)?.length ?? 0;
|
|
1140
1144
|
}
|
|
1141
1145
|
var noticeSink = (message) => {
|
|
@@ -1143,8 +1147,8 @@ var noticeSink = (message) => {
|
|
|
1143
1147
|
`);
|
|
1144
1148
|
};
|
|
1145
1149
|
var noticed = false;
|
|
1146
|
-
function setContextBudgetNotice(
|
|
1147
|
-
noticeSink =
|
|
1150
|
+
function setContextBudgetNotice(sink2) {
|
|
1151
|
+
noticeSink = sink2;
|
|
1148
1152
|
}
|
|
1149
1153
|
function resetContextBudgetNotices() {
|
|
1150
1154
|
noticed = false;
|
|
@@ -2044,6 +2048,10 @@ var UltracProvider = class {
|
|
|
2044
2048
|
if (chunk.choices[0]?.finish_reason) finishReason = chunk.choices[0].finish_reason;
|
|
2045
2049
|
const delta = chunk.choices[0]?.delta;
|
|
2046
2050
|
if (!delta) continue;
|
|
2051
|
+
const reasoning = delta.reasoning_content;
|
|
2052
|
+
if (delta.content || reasoning || delta.tool_calls?.some(
|
|
2053
|
+
(part) => part.id || part.function?.name || part.function?.arguments
|
|
2054
|
+
)) request.onProgress?.();
|
|
2047
2055
|
if (delta.content) content += delta.content;
|
|
2048
2056
|
for (const part of delta.tool_calls ?? []) {
|
|
2049
2057
|
const call = calls.get(part.index) ?? { id: "", name: "", arguments: "" };
|
|
@@ -2199,8 +2207,8 @@ async function validateSnapshotWithin(snapshot, timeoutMs) {
|
|
|
2199
2207
|
try {
|
|
2200
2208
|
return await Promise.race([
|
|
2201
2209
|
snapshot.validate(),
|
|
2202
|
-
new Promise((
|
|
2203
|
-
timer = setTimeout(() =>
|
|
2210
|
+
new Promise((resolve17) => {
|
|
2211
|
+
timer = setTimeout(() => resolve17(void 0), timeoutMs);
|
|
2204
2212
|
})
|
|
2205
2213
|
]);
|
|
2206
2214
|
} finally {
|
|
@@ -2264,8 +2272,8 @@ async function settleWithin(promise, timeoutMs) {
|
|
|
2264
2272
|
try {
|
|
2265
2273
|
await Promise.race([
|
|
2266
2274
|
Promise.resolve(promise).catch(() => void 0),
|
|
2267
|
-
new Promise((
|
|
2268
|
-
timer = setTimeout(
|
|
2275
|
+
new Promise((resolve17) => {
|
|
2276
|
+
timer = setTimeout(resolve17, timeoutMs);
|
|
2269
2277
|
})
|
|
2270
2278
|
]);
|
|
2271
2279
|
} finally {
|
|
@@ -2309,10 +2317,11 @@ function toChatMessages(request) {
|
|
|
2309
2317
|
];
|
|
2310
2318
|
}
|
|
2311
2319
|
function toChatMessage(message) {
|
|
2312
|
-
return {
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2320
|
+
if (message.role === "assistant") return { role: "assistant", content: message.content };
|
|
2321
|
+
return { role: "user", content: message.image ? [
|
|
2322
|
+
{ type: "text", text: message.content },
|
|
2323
|
+
{ type: "image_url", image_url: { url: message.image.url } }
|
|
2324
|
+
] : message.content };
|
|
2316
2325
|
}
|
|
2317
2326
|
function toNativeChatMessage(message) {
|
|
2318
2327
|
if (message.role === "tool") {
|
|
@@ -2332,6 +2341,10 @@ function toNativeChatMessage(message) {
|
|
|
2332
2341
|
}))
|
|
2333
2342
|
};
|
|
2334
2343
|
}
|
|
2344
|
+
if (message.role === "user" && message.image) return { role: "user", content: [
|
|
2345
|
+
{ type: "text", text: message.content },
|
|
2346
|
+
{ type: "image_url", image_url: { url: message.image.url } }
|
|
2347
|
+
] };
|
|
2335
2348
|
return { role: message.role, content: message.content };
|
|
2336
2349
|
}
|
|
2337
2350
|
function toOpenAiTool(tool) {
|
|
@@ -2482,7 +2495,7 @@ var FallbackConfigSchema = z3.object({
|
|
|
2482
2495
|
backoffFactor: z3.number().min(1).max(10).default(2),
|
|
2483
2496
|
/** Randomize backoff to avoid synchronized retries. */
|
|
2484
2497
|
jitter: z3.boolean().default(true),
|
|
2485
|
-
/** Per-attempt timeout;
|
|
2498
|
+
/** Per-attempt timeout; text streams bound first-token time, native streams bound inactivity. 0 disables it. */
|
|
2486
2499
|
requestTimeoutMs: z3.number().int().min(0).max(6e5).default(12e4)
|
|
2487
2500
|
});
|
|
2488
2501
|
var ProviderTimeoutError = class extends Error {
|
|
@@ -2723,17 +2736,17 @@ function computeBackoffDelay(attempt, options) {
|
|
|
2723
2736
|
return Math.round(capped * (0.5 + random() * 0.5));
|
|
2724
2737
|
}
|
|
2725
2738
|
function defaultSleep(ms, cancel) {
|
|
2726
|
-
return new Promise((
|
|
2739
|
+
return new Promise((resolve17) => {
|
|
2727
2740
|
if (cancel?.aborted) {
|
|
2728
|
-
|
|
2741
|
+
resolve17();
|
|
2729
2742
|
return;
|
|
2730
2743
|
}
|
|
2731
|
-
const timer = setTimeout(
|
|
2744
|
+
const timer = setTimeout(resolve17, ms);
|
|
2732
2745
|
cancel?.addEventListener(
|
|
2733
2746
|
"abort",
|
|
2734
2747
|
() => {
|
|
2735
2748
|
clearTimeout(timer);
|
|
2736
|
-
|
|
2749
|
+
resolve17();
|
|
2737
2750
|
},
|
|
2738
2751
|
{ once: true }
|
|
2739
2752
|
);
|
|
@@ -2808,10 +2821,19 @@ var FallbackProvider = class {
|
|
|
2808
2821
|
);
|
|
2809
2822
|
}
|
|
2810
2823
|
async chatWithTools(request) {
|
|
2811
|
-
return this.run("chatWithTools", request.signal, request.model, (entry, model, signal) => {
|
|
2824
|
+
return this.run("chatWithTools", request.signal, request.model, (entry, model, signal, touch) => {
|
|
2812
2825
|
if (entry.provider.supportsToolCalling && entry.provider.chatWithTools) {
|
|
2813
|
-
return entry.provider.chatWithTools({
|
|
2826
|
+
return entry.provider.chatWithTools({
|
|
2827
|
+
...request,
|
|
2828
|
+
model,
|
|
2829
|
+
signal,
|
|
2830
|
+
onProgress: () => {
|
|
2831
|
+
touch();
|
|
2832
|
+
request.onProgress?.();
|
|
2833
|
+
}
|
|
2834
|
+
});
|
|
2814
2835
|
}
|
|
2836
|
+
if (request.messages.some((message) => "image" in message && message.image)) throw Object.assign(new Error("This fallback provider cannot inspect image attachments."), { status: 400 });
|
|
2815
2837
|
return this.chatThroughToolProtocol(entry, model, request, signal);
|
|
2816
2838
|
});
|
|
2817
2839
|
}
|
|
@@ -2896,7 +2918,8 @@ var FallbackProvider = class {
|
|
|
2896
2918
|
const result = await this.withDeadline(
|
|
2897
2919
|
entry.name,
|
|
2898
2920
|
signal,
|
|
2899
|
-
(attemptSignal) => invoke(entry, model, attemptSignal)
|
|
2921
|
+
(attemptSignal, touch) => invoke(entry, model, attemptSignal, touch),
|
|
2922
|
+
operation === "chatWithTools"
|
|
2900
2923
|
);
|
|
2901
2924
|
this.active = target;
|
|
2902
2925
|
trace.end({ outcome: "ok" });
|
|
@@ -3116,10 +3139,10 @@ var FallbackProvider = class {
|
|
|
3116
3139
|
error.name = "AbortError";
|
|
3117
3140
|
throw error;
|
|
3118
3141
|
}
|
|
3119
|
-
async withDeadline(provider, signal, run) {
|
|
3120
|
-
const deadline = this.createDeadline(provider, signal);
|
|
3142
|
+
async withDeadline(provider, signal, run, inactivity = false) {
|
|
3143
|
+
const deadline = this.createDeadline(provider, signal, inactivity);
|
|
3121
3144
|
try {
|
|
3122
|
-
return await deadline.race(run(deadline.signal));
|
|
3145
|
+
return await deadline.race(run(deadline.signal, deadline.touch));
|
|
3123
3146
|
} finally {
|
|
3124
3147
|
deadline.clear();
|
|
3125
3148
|
}
|
|
@@ -3129,31 +3152,55 @@ var FallbackProvider = class {
|
|
|
3129
3152
|
* only ever rejects while the deadline is live, so clearing it can never leave
|
|
3130
3153
|
* an unhandled rejection behind.
|
|
3131
3154
|
*/
|
|
3132
|
-
createDeadline(provider, signal) {
|
|
3155
|
+
createDeadline(provider, signal, inactivity = false) {
|
|
3133
3156
|
if (this.requestTimeoutMs <= 0) {
|
|
3134
|
-
return { signal, race: (promise) => promise, clear: () => void 0 };
|
|
3157
|
+
return { signal, race: (promise) => promise, clear: () => void 0, touch: () => void 0 };
|
|
3135
3158
|
}
|
|
3136
3159
|
const controller = new AbortController();
|
|
3137
|
-
|
|
3160
|
+
let cleared = false;
|
|
3161
|
+
let idleTimer;
|
|
3162
|
+
const hardTimer = new AbortController();
|
|
3163
|
+
let rejectExpired;
|
|
3164
|
+
const expired = new Promise((_, reject) => {
|
|
3165
|
+
rejectExpired = reject;
|
|
3166
|
+
});
|
|
3167
|
+
const forwardAbort = () => {
|
|
3168
|
+
const reason = signal?.reason ?? Object.assign(new Error("Request was aborted."), { name: "AbortError" });
|
|
3169
|
+
rejectExpired(reason);
|
|
3170
|
+
controller.abort(reason);
|
|
3171
|
+
};
|
|
3172
|
+
const expire = (timeoutMs) => {
|
|
3173
|
+
const error = new ProviderTimeoutError(timeoutMs, provider);
|
|
3174
|
+
rejectExpired(error);
|
|
3175
|
+
controller.abort(error);
|
|
3176
|
+
};
|
|
3177
|
+
const touch = () => {
|
|
3178
|
+
if (cleared || controller.signal.aborted) return;
|
|
3179
|
+
idleTimer?.abort();
|
|
3180
|
+
const timer = new AbortController();
|
|
3181
|
+
idleTimer = timer;
|
|
3182
|
+
void this.sleep(this.requestTimeoutMs, timer.signal).then(() => {
|
|
3183
|
+
if (!cleared && !timer.signal.aborted) expire(this.requestTimeoutMs);
|
|
3184
|
+
});
|
|
3185
|
+
};
|
|
3186
|
+
touch();
|
|
3187
|
+
if (inactivity) {
|
|
3188
|
+
void this.sleep(3e5, hardTimer.signal).then(() => {
|
|
3189
|
+
if (!cleared && !hardTimer.signal.aborted) expire(3e5);
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3138
3192
|
if (signal) {
|
|
3139
3193
|
if (signal.aborted) forwardAbort();
|
|
3140
3194
|
else signal.addEventListener("abort", forwardAbort, { once: true });
|
|
3141
3195
|
}
|
|
3142
|
-
let cleared = false;
|
|
3143
|
-
const cancelDeadline = new AbortController();
|
|
3144
|
-
const expired = new Promise((_, reject) => {
|
|
3145
|
-
void this.sleep(this.requestTimeoutMs, cancelDeadline.signal).then(() => {
|
|
3146
|
-
if (cleared) return;
|
|
3147
|
-
controller.abort();
|
|
3148
|
-
reject(new ProviderTimeoutError(this.requestTimeoutMs, provider));
|
|
3149
|
-
});
|
|
3150
|
-
});
|
|
3151
3196
|
return {
|
|
3152
3197
|
signal: controller.signal,
|
|
3153
3198
|
race: (promise) => Promise.race([promise, expired]),
|
|
3199
|
+
touch: inactivity ? touch : () => void 0,
|
|
3154
3200
|
clear: () => {
|
|
3155
3201
|
cleared = true;
|
|
3156
|
-
|
|
3202
|
+
idleTimer?.abort();
|
|
3203
|
+
hardTimer.abort();
|
|
3157
3204
|
signal?.removeEventListener("abort", forwardAbort);
|
|
3158
3205
|
}
|
|
3159
3206
|
};
|
|
@@ -4066,7 +4113,7 @@ function runProcess(command, args, options) {
|
|
|
4066
4113
|
});
|
|
4067
4114
|
}
|
|
4068
4115
|
const { env: env2, redactedEnvKeys } = createSanitizedProcessEnv();
|
|
4069
|
-
return new Promise((
|
|
4116
|
+
return new Promise((resolve17, reject) => {
|
|
4070
4117
|
const child = spawn(command, args, {
|
|
4071
4118
|
cwd: options.cwd,
|
|
4072
4119
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -4151,7 +4198,7 @@ function runProcess(command, args, options) {
|
|
|
4151
4198
|
child.on("error", (error) => {
|
|
4152
4199
|
finish(() => {
|
|
4153
4200
|
if (termination === "aborted") {
|
|
4154
|
-
|
|
4201
|
+
resolve17({
|
|
4155
4202
|
stdout: stdout6.text(),
|
|
4156
4203
|
stderr: stderr2.text(),
|
|
4157
4204
|
started,
|
|
@@ -4173,7 +4220,7 @@ function runProcess(command, args, options) {
|
|
|
4173
4220
|
const stdoutTruncation = stdout6.truncation();
|
|
4174
4221
|
const stderrTruncation = stderr2.truncation();
|
|
4175
4222
|
finish(
|
|
4176
|
-
() =>
|
|
4223
|
+
() => resolve17({
|
|
4177
4224
|
stdout: stdout6.text(),
|
|
4178
4225
|
stderr: stderr2.text(),
|
|
4179
4226
|
started,
|
|
@@ -5185,7 +5232,7 @@ function topCounts(values) {
|
|
|
5185
5232
|
for (const value of values) {
|
|
5186
5233
|
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
5187
5234
|
}
|
|
5188
|
-
return [...counts.entries()].map(([name,
|
|
5235
|
+
return [...counts.entries()].map(([name, count3]) => ({ name, count: count3 })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
5189
5236
|
}
|
|
5190
5237
|
async function collectGitContext(workspace, signal) {
|
|
5191
5238
|
const statusResult = await runGit(["status", "--short", "--branch"], { cwd: workspace, signal });
|
|
@@ -5377,7 +5424,7 @@ var diagnosticsTool = {
|
|
|
5377
5424
|
const byFile = summarizeByFile(matched);
|
|
5378
5425
|
const records = matched.slice(0, input.maxRecords);
|
|
5379
5426
|
const checkerExit = runs[0].exitCode;
|
|
5380
|
-
const
|
|
5427
|
+
const clean2 = matched.length === 0;
|
|
5381
5428
|
const unparsed = matched.length === 0 && runs.some((run) => run.exitCode !== 0);
|
|
5382
5429
|
const rawTail = unparsed ? tail(runs.find((run) => run.exitCode !== 0)?.output ?? "", 2e3) : void 0;
|
|
5383
5430
|
const lines = [
|
|
@@ -5394,9 +5441,9 @@ var diagnosticsTool = {
|
|
|
5394
5441
|
...byFile.length ? byFile.slice(0, input.maxFiles).map((entry) => `- ${entry.file}: ${entry.errors} error(s), ${entry.warnings} warning(s)`) : ["- none"],
|
|
5395
5442
|
byFile.length > input.maxFiles ? `- ...${byFile.length - input.maxFiles} more file(s)` : void 0,
|
|
5396
5443
|
"",
|
|
5397
|
-
|
|
5398
|
-
...
|
|
5399
|
-
!
|
|
5444
|
+
clean2 ? unparsed ? "No diagnostics parsed, but the checker exited non-zero. Raw output tail:" : "No diagnostics." : `Diagnostics (showing ${records.length} of ${matched.length}):`,
|
|
5445
|
+
...clean2 ? rawTail ? [rawTail] : [] : records.map(renderDiagnostic),
|
|
5446
|
+
!clean2 && matched.length > records.length ? `...${matched.length - records.length} more suppressed` : void 0
|
|
5400
5447
|
].filter((line) => line !== void 0);
|
|
5401
5448
|
return {
|
|
5402
5449
|
ok: !runs.some((run) => run.aborted) && (matched.length > 0 || runs.every((run) => run.exitCode === 0)),
|
|
@@ -5676,8 +5723,8 @@ function normalizeFilters(workspace, paths) {
|
|
|
5676
5723
|
}
|
|
5677
5724
|
function normalizeDiagnosticPath(workspace, diagnostic) {
|
|
5678
5725
|
try {
|
|
5679
|
-
const
|
|
5680
|
-
return { ...diagnostic, file:
|
|
5726
|
+
const relative3 = toWorkspaceRelative(workspace, resolveInsideWorkspace(workspace, diagnostic.file));
|
|
5727
|
+
return { ...diagnostic, file: relative3.replaceAll("\\", "/") };
|
|
5681
5728
|
} catch {
|
|
5682
5729
|
return { ...diagnostic, file: diagnostic.file.replaceAll("\\", "/") };
|
|
5683
5730
|
}
|
|
@@ -6090,6 +6137,21 @@ var listFilesTool = {
|
|
|
6090
6137
|
}
|
|
6091
6138
|
};
|
|
6092
6139
|
var RANGE_MARKER_RESERVE_CHARS = 300;
|
|
6140
|
+
async function resolveReadableFile(workspace, path) {
|
|
6141
|
+
try {
|
|
6142
|
+
const target = await resolveReadablePath(workspace, path);
|
|
6143
|
+
const fileStat = await stat4(target);
|
|
6144
|
+
if (!fileStat.isFile()) throw new Error(`Not a file: ${path}`);
|
|
6145
|
+
return { target, fileStat };
|
|
6146
|
+
} catch (error) {
|
|
6147
|
+
const code = error.code;
|
|
6148
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
6149
|
+
throw new Error(
|
|
6150
|
+
`File not found: ${JSON.stringify(path)}. Use list_files to discover the exact workspace path or search_files to locate relevant code, then retry with a confirmed path. No alternate file was read.`,
|
|
6151
|
+
{ cause: error }
|
|
6152
|
+
);
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6093
6155
|
async function readFilePrefix(target, maxBytes) {
|
|
6094
6156
|
const handle = await open3(target, "r");
|
|
6095
6157
|
try {
|
|
@@ -6148,11 +6210,7 @@ var readFileTool = {
|
|
|
6148
6210
|
description: "Read a UTF-8 text file from inside the workspace. Reads a line range (startLine, maxLines) when the whole file would not fit the tool-result cap, and says where to continue.",
|
|
6149
6211
|
inputSchema: ReadFileInput,
|
|
6150
6212
|
async execute(input, context) {
|
|
6151
|
-
const target = await
|
|
6152
|
-
const fileStat = await stat4(target);
|
|
6153
|
-
if (!fileStat.isFile()) {
|
|
6154
|
-
throw new Error(`Not a file: ${input.path}`);
|
|
6155
|
-
}
|
|
6213
|
+
const { target } = await resolveReadableFile(context.workspace, input.path);
|
|
6156
6214
|
const { buffer: clipped, totalBytes, stable } = await readFilePrefix(target, input.maxBytes);
|
|
6157
6215
|
const text = clipped.toString("utf8");
|
|
6158
6216
|
const clippedByBytes = totalBytes > clipped.byteLength;
|
|
@@ -6248,11 +6306,7 @@ var readManyFilesTool = {
|
|
|
6248
6306
|
const sections = [];
|
|
6249
6307
|
let remainingBytes = input.maxTotalBytes;
|
|
6250
6308
|
for (const path of paths) {
|
|
6251
|
-
const target = await
|
|
6252
|
-
const fileStat = await stat4(target);
|
|
6253
|
-
if (!fileStat.isFile()) {
|
|
6254
|
-
throw new Error(`Not a file: ${path}`);
|
|
6255
|
-
}
|
|
6309
|
+
const { target, fileStat } = await resolveReadableFile(context.workspace, path);
|
|
6256
6310
|
const readBudget = Math.min(input.maxBytesPerFile, remainingBytes);
|
|
6257
6311
|
if (readBudget <= 0) {
|
|
6258
6312
|
files.push({ path, bytesRead: 0, totalBytes: fileStat.size, truncated: true });
|
|
@@ -6865,12 +6919,12 @@ var builtinSecretRules = [
|
|
|
6865
6919
|
];
|
|
6866
6920
|
async function resolveArtifact(workspace, requestedPath) {
|
|
6867
6921
|
const absolute = await resolveInsideWorkspaceReal(workspace, requestedPath);
|
|
6868
|
-
const
|
|
6922
|
+
const relative3 = toWorkspaceRelative(workspace, absolute);
|
|
6869
6923
|
const info = await stat5(absolute);
|
|
6870
6924
|
if (!info.isFile()) {
|
|
6871
6925
|
throw new Error(`Not a file: ${requestedPath}`);
|
|
6872
6926
|
}
|
|
6873
|
-
return { absolute, relative:
|
|
6927
|
+
return { absolute, relative: relative3 };
|
|
6874
6928
|
}
|
|
6875
6929
|
function formatMode(mode) {
|
|
6876
6930
|
return (mode & 511).toString(8).padStart(3, "0");
|
|
@@ -6970,8 +7024,8 @@ function shannonEntropy(value) {
|
|
|
6970
7024
|
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
6971
7025
|
}
|
|
6972
7026
|
let entropy = 0;
|
|
6973
|
-
for (const
|
|
6974
|
-
const probability =
|
|
7027
|
+
for (const count3 of counts.values()) {
|
|
7028
|
+
const probability = count3 / value.length;
|
|
6975
7029
|
entropy -= probability * Math.log2(probability);
|
|
6976
7030
|
}
|
|
6977
7031
|
return entropy;
|
|
@@ -8796,7 +8850,7 @@ function summarizeDirectories(files) {
|
|
|
8796
8850
|
const directory = dirname9(file.path).split(/[\\/]/)[0] || ".";
|
|
8797
8851
|
counts.set(directory, (counts.get(directory) ?? 0) + 1);
|
|
8798
8852
|
}
|
|
8799
|
-
return [...counts.entries()].map(([name,
|
|
8853
|
+
return [...counts.entries()].map(([name, count3]) => ({ name, count: count3 })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
8800
8854
|
}
|
|
8801
8855
|
function summarizeExtensions(files) {
|
|
8802
8856
|
const summaries = /* @__PURE__ */ new Map();
|
|
@@ -9349,18 +9403,18 @@ function isConcurrencySafe(tool) {
|
|
|
9349
9403
|
}
|
|
9350
9404
|
function planToolBatches(calls, tools) {
|
|
9351
9405
|
const batches = [];
|
|
9352
|
-
let
|
|
9406
|
+
let open11;
|
|
9353
9407
|
for (const call of calls) {
|
|
9354
9408
|
if (!isConcurrencySafe(tools.get(call.tool))) {
|
|
9355
9409
|
batches.push([call]);
|
|
9356
|
-
|
|
9410
|
+
open11 = void 0;
|
|
9357
9411
|
continue;
|
|
9358
9412
|
}
|
|
9359
|
-
if (!
|
|
9360
|
-
|
|
9361
|
-
batches.push(
|
|
9413
|
+
if (!open11) {
|
|
9414
|
+
open11 = [];
|
|
9415
|
+
batches.push(open11);
|
|
9362
9416
|
}
|
|
9363
|
-
|
|
9417
|
+
open11.push(call);
|
|
9364
9418
|
}
|
|
9365
9419
|
return batches;
|
|
9366
9420
|
}
|
|
@@ -9855,7 +9909,7 @@ async function acquireLock(directory, waitMs) {
|
|
|
9855
9909
|
};
|
|
9856
9910
|
} catch (error) {
|
|
9857
9911
|
if (error.code !== "EEXIST" || Date.now() >= deadline) throw error;
|
|
9858
|
-
await new Promise((
|
|
9912
|
+
await new Promise((resolve17) => setTimeout(resolve17, 10));
|
|
9859
9913
|
}
|
|
9860
9914
|
}
|
|
9861
9915
|
}
|
|
@@ -9903,6 +9957,8 @@ function readSessionContextSnapshot(session) {
|
|
|
9903
9957
|
const snapshot = session.contextSnapshot;
|
|
9904
9958
|
if (!snapshot || typeof snapshot !== "object" || snapshot.version !== 1 || !["native", "text", "run"].includes(snapshot.kind) || !Number.isSafeInteger(snapshot.completedSteps) || snapshot.completedSteps < 0 || !Array.isArray(snapshot.messages) || snapshot.messages.some((message) => {
|
|
9905
9959
|
if (!message || typeof message !== "object" || !["user", "assistant", "system", "tool"].includes(message.role) || typeof message.content !== "string") return true;
|
|
9960
|
+
const image = message.image;
|
|
9961
|
+
if (image !== void 0 && (message.role !== "user" || !image || typeof image.url !== "string" || image.url.length > 7 * 1024 * 1024 || !/^data:image\/(?:png|jpeg|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(image.url))) return true;
|
|
9906
9962
|
const native = message;
|
|
9907
9963
|
return snapshot.kind !== "native" && (message.role === "system" || native.toolCalls !== void 0 || native.toolCallId !== void 0) || native.toolCalls !== void 0 && message.role !== "assistant" || native.toolCallId !== void 0 && message.role !== "tool" || snapshot.kind === "native" && message.role === "tool" && typeof native.toolCallId !== "string" || native.toolCallId !== void 0 && typeof native.toolCallId !== "string" || native.toolCalls !== void 0 && (!Array.isArray(native.toolCalls) || native.toolCalls.some((call) => !call || typeof call.id !== "string" || typeof call.name !== "string" || typeof call.arguments !== "string"));
|
|
9908
9964
|
}) || !completeContextUnits(snapshot.messages) || (readSessionUserConstraints(session) ?? [{ content: session.task }]).some((constraint) => !snapshot.messages.some((message) => message.role === "user" && message.content === constraint.content))) throw new Error(`Session ${session.id} has an invalid saved context snapshot and cannot be continued safely.`);
|
|
@@ -11144,10 +11200,10 @@ async function withHardTimeout(promise, ms, onTimeout) {
|
|
|
11144
11200
|
(error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })
|
|
11145
11201
|
);
|
|
11146
11202
|
let timer;
|
|
11147
|
-
const expiry = new Promise((
|
|
11203
|
+
const expiry = new Promise((resolve17) => {
|
|
11148
11204
|
timer = setTimeout(() => {
|
|
11149
11205
|
onTimeout?.();
|
|
11150
|
-
|
|
11206
|
+
resolve17(void 0);
|
|
11151
11207
|
}, ms);
|
|
11152
11208
|
});
|
|
11153
11209
|
try {
|
|
@@ -12304,8 +12360,8 @@ function findFileInText(text, workspace) {
|
|
|
12304
12360
|
for (const match of text.matchAll(stackFrame)) {
|
|
12305
12361
|
const candidate = match[1];
|
|
12306
12362
|
if (candidate.includes("node_modules") || candidate.includes("/internal/")) continue;
|
|
12307
|
-
const
|
|
12308
|
-
if (
|
|
12363
|
+
const relative3 = relativizeFile(candidate, workspace);
|
|
12364
|
+
if (relative3 !== "unknown") return relative3;
|
|
12309
12365
|
}
|
|
12310
12366
|
return null;
|
|
12311
12367
|
}
|
|
@@ -12793,12 +12849,12 @@ async function sendRequest(url, input, approved, signal) {
|
|
|
12793
12849
|
// The socket goes to a pinned address; TLS still verifies the name.
|
|
12794
12850
|
servername: secure && !isIP(hostname) ? hostname : void 0
|
|
12795
12851
|
};
|
|
12796
|
-
return await new Promise((
|
|
12852
|
+
return await new Promise((resolve17, reject) => {
|
|
12797
12853
|
const request = secure ? httpsRequest(options) : httpRequest(options);
|
|
12798
12854
|
request.on("response", (response) => {
|
|
12799
12855
|
response.on("error", () => {
|
|
12800
12856
|
});
|
|
12801
|
-
|
|
12857
|
+
resolve17({
|
|
12802
12858
|
status: response.statusCode ?? 0,
|
|
12803
12859
|
statusText: response.statusMessage ?? "",
|
|
12804
12860
|
ok: (response.statusCode ?? 0) >= 200 && (response.statusCode ?? 0) < 300,
|
|
@@ -13277,8 +13333,8 @@ var LOOPBACK_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
|
13277
13333
|
async function startLoopbackServer(options) {
|
|
13278
13334
|
const timeoutMs = options.timeoutMs ?? LOOPBACK_TIMEOUT_MS;
|
|
13279
13335
|
let settle;
|
|
13280
|
-
const wait = new Promise((
|
|
13281
|
-
settle =
|
|
13336
|
+
const wait = new Promise((resolve17) => {
|
|
13337
|
+
settle = resolve17;
|
|
13282
13338
|
});
|
|
13283
13339
|
const server = http.createServer((request, response) => {
|
|
13284
13340
|
const host = request.headers.host ?? "127.0.0.1";
|
|
@@ -13325,10 +13381,10 @@ async function startLoopbackServer(options) {
|
|
|
13325
13381
|
settle?.({ ok: false, error: "server_error", description: error.message });
|
|
13326
13382
|
settle = void 0;
|
|
13327
13383
|
});
|
|
13328
|
-
const port = await new Promise((
|
|
13384
|
+
const port = await new Promise((resolve17, reject) => {
|
|
13329
13385
|
server.listen(0, "127.0.0.1", () => {
|
|
13330
13386
|
const address = server.address();
|
|
13331
|
-
if (address && typeof address === "object")
|
|
13387
|
+
if (address && typeof address === "object") resolve17(address.port);
|
|
13332
13388
|
else reject(new Error("loopback server did not bind a port"));
|
|
13333
13389
|
});
|
|
13334
13390
|
server.once("error", reject);
|
|
@@ -13347,9 +13403,9 @@ async function startLoopbackServer(options) {
|
|
|
13347
13403
|
port,
|
|
13348
13404
|
redirectUri: `http://127.0.0.1:${port}${LOOPBACK_PATH}`,
|
|
13349
13405
|
wait,
|
|
13350
|
-
close: () => new Promise((
|
|
13406
|
+
close: () => new Promise((resolve17) => {
|
|
13351
13407
|
clearTimeout(timer);
|
|
13352
|
-
server.close(() =>
|
|
13408
|
+
server.close(() => resolve17());
|
|
13353
13409
|
})
|
|
13354
13410
|
};
|
|
13355
13411
|
}
|
|
@@ -13463,6 +13519,22 @@ function errorMessage(error) {
|
|
|
13463
13519
|
}
|
|
13464
13520
|
return String(error);
|
|
13465
13521
|
}
|
|
13522
|
+
async function readAccountPlan(credentials, fetchImpl = (input, init) => fetch(input, init)) {
|
|
13523
|
+
if (!credentials) return void 0;
|
|
13524
|
+
try {
|
|
13525
|
+
const response = await fetchImpl(modelsUrl(credentials.baseUrl), {
|
|
13526
|
+
headers: { authorization: `Bearer ${credentials.apiKey}`, accept: "application/json" },
|
|
13527
|
+
redirect: "error",
|
|
13528
|
+
signal: AbortSignal.timeout(2e3)
|
|
13529
|
+
});
|
|
13530
|
+
if (!response.ok) return void 0;
|
|
13531
|
+
const body = await response.json();
|
|
13532
|
+
const plans = { free: "Free", pro: "Pro", team: "Team", ultra: "Ultra" };
|
|
13533
|
+
return typeof body.account?.plan === "string" && Object.hasOwn(plans, body.account.plan) ? plans[body.account.plan] : void 0;
|
|
13534
|
+
} catch {
|
|
13535
|
+
return void 0;
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13466
13538
|
|
|
13467
13539
|
// src/auth/browser-login.ts
|
|
13468
13540
|
var BROWSER_LOGIN_REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -13660,7 +13732,7 @@ function silenceTimeoutMs() {
|
|
|
13660
13732
|
return Number.isFinite(override) && override > 0 ? override : DEFAULT_NON_TTY_SILENCE_TIMEOUT_MS;
|
|
13661
13733
|
}
|
|
13662
13734
|
function ask(rl, question, onSettle) {
|
|
13663
|
-
return new Promise((
|
|
13735
|
+
return new Promise((resolve17) => {
|
|
13664
13736
|
let settled = false;
|
|
13665
13737
|
let silenceTimer;
|
|
13666
13738
|
const keepAwake = () => {
|
|
@@ -13674,7 +13746,7 @@ function ask(rl, question, onSettle) {
|
|
|
13674
13746
|
stdin2.off("data", keepAwake);
|
|
13675
13747
|
rl.close();
|
|
13676
13748
|
onSettle?.();
|
|
13677
|
-
|
|
13749
|
+
resolve17(value.trim());
|
|
13678
13750
|
};
|
|
13679
13751
|
if (!stdin2.isTTY) {
|
|
13680
13752
|
silenceTimer = setTimeout(() => finish(""), silenceTimeoutMs());
|
|
@@ -14026,9 +14098,9 @@ var JsonRpcEndpoint = class {
|
|
|
14026
14098
|
if (signal?.aborted) {
|
|
14027
14099
|
return Promise.reject(new JsonRpcRequestAbortedError(method, void 0, false));
|
|
14028
14100
|
}
|
|
14029
|
-
return new Promise((
|
|
14101
|
+
return new Promise((resolve17, reject) => {
|
|
14030
14102
|
const limit = timeoutMs ?? this.defaultTimeoutMs;
|
|
14031
|
-
const entry = { method, resolve:
|
|
14103
|
+
const entry = { method, resolve: resolve17, reject, signal, dispatched: false };
|
|
14032
14104
|
if (signal) {
|
|
14033
14105
|
entry.abortHandler = () => {
|
|
14034
14106
|
this.settle(id, () => reject(new JsonRpcRequestAbortedError(method, id, entry.dispatched)));
|
|
@@ -14711,13 +14783,13 @@ function renderMcpStatuses(statuses) {
|
|
|
14711
14783
|
}).join("\n");
|
|
14712
14784
|
}
|
|
14713
14785
|
function withTimeout(promise, ms, message) {
|
|
14714
|
-
return new Promise((
|
|
14786
|
+
return new Promise((resolve17, reject) => {
|
|
14715
14787
|
const timer = setTimeout(() => reject(new Error(`${message} within ${ms}ms.`)), ms);
|
|
14716
14788
|
timer.unref?.();
|
|
14717
14789
|
promise.then(
|
|
14718
14790
|
(value) => {
|
|
14719
14791
|
clearTimeout(timer);
|
|
14720
|
-
|
|
14792
|
+
resolve17(value);
|
|
14721
14793
|
},
|
|
14722
14794
|
(error) => {
|
|
14723
14795
|
clearTimeout(timer);
|
|
@@ -15116,14 +15188,14 @@ function redact(secret) {
|
|
|
15116
15188
|
// src/notify/channel.ts
|
|
15117
15189
|
function sleep(ms, signal) {
|
|
15118
15190
|
if (signal?.aborted) return Promise.resolve(false);
|
|
15119
|
-
return new Promise((
|
|
15191
|
+
return new Promise((resolve17) => {
|
|
15120
15192
|
const timer = setTimeout(() => {
|
|
15121
15193
|
signal?.removeEventListener("abort", onAbort);
|
|
15122
|
-
|
|
15194
|
+
resolve17(true);
|
|
15123
15195
|
}, ms);
|
|
15124
15196
|
const onAbort = () => {
|
|
15125
15197
|
clearTimeout(timer);
|
|
15126
|
-
|
|
15198
|
+
resolve17(false);
|
|
15127
15199
|
};
|
|
15128
15200
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15129
15201
|
});
|
|
@@ -15371,7 +15443,7 @@ ${question}${optionLines}`, signal);
|
|
|
15371
15443
|
await Promise.allSettled(this.channels.map((channel) => channel.call?.(spoken, wait.signal)));
|
|
15372
15444
|
})();
|
|
15373
15445
|
const timeout = sleep(this.timing.timeoutMs, wait.signal).then(() => null);
|
|
15374
|
-
const cancelled = new Promise((
|
|
15446
|
+
const cancelled = new Promise((resolve17) => wait.signal.addEventListener("abort", () => resolve17(null), { once: true }));
|
|
15375
15447
|
const reply = await Promise.race([...replies, timeout, cancelled]);
|
|
15376
15448
|
return reply === null ? null : pickOption(reply, options);
|
|
15377
15449
|
} finally {
|
|
@@ -15682,6 +15754,80 @@ function registerTrustCommand(program2) {
|
|
|
15682
15754
|
});
|
|
15683
15755
|
}
|
|
15684
15756
|
|
|
15757
|
+
// src/core/image-attachments.ts
|
|
15758
|
+
import { constants as constants6 } from "fs";
|
|
15759
|
+
import { open as open9 } from "fs/promises";
|
|
15760
|
+
import { homedir as homedir2 } from "os";
|
|
15761
|
+
import { isAbsolute as isAbsolute4 } from "path";
|
|
15762
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15763
|
+
import sharp from "sharp";
|
|
15764
|
+
var MAX_SOURCE_BYTES = 20 * 1024 * 1024;
|
|
15765
|
+
function imageDrops(text) {
|
|
15766
|
+
const drops = [];
|
|
15767
|
+
const tokens = /"(?:\\.|[^"\\])*"|'[^']*'|(?:\\.|[^\s])+/g;
|
|
15768
|
+
for (const match of text.matchAll(tokens)) {
|
|
15769
|
+
const token = match[0];
|
|
15770
|
+
let path = token.startsWith("'") ? token.slice(1, -1) : token.startsWith('"') ? token.slice(1, -1).replace(/\\([\\"$`])/g, "$1") : token.replace(/\\(.)/g, "$1");
|
|
15771
|
+
if (path.startsWith("file://")) {
|
|
15772
|
+
try {
|
|
15773
|
+
path = fileURLToPath2(path);
|
|
15774
|
+
} catch {
|
|
15775
|
+
continue;
|
|
15776
|
+
}
|
|
15777
|
+
}
|
|
15778
|
+
if (path.startsWith("~/")) path = `${homedir2()}/${path.slice(2)}`;
|
|
15779
|
+
if (!isAbsolute4(path) || !/\.(png|jpe?g|webp)$/i.test(path)) continue;
|
|
15780
|
+
drops.push({ start: match.index, end: match.index + token.length, path, label: `[Image ${drops.length + 1}]` });
|
|
15781
|
+
}
|
|
15782
|
+
return drops;
|
|
15783
|
+
}
|
|
15784
|
+
function formatImageInput(text, cursor = text.length) {
|
|
15785
|
+
let display = "";
|
|
15786
|
+
let previous = 0;
|
|
15787
|
+
let shift = 0;
|
|
15788
|
+
let adjusted = cursor;
|
|
15789
|
+
for (const drop of imageDrops(text)) {
|
|
15790
|
+
display += text.slice(previous, drop.start) + drop.label;
|
|
15791
|
+
if (cursor >= drop.end) adjusted += drop.label.length - (drop.end - drop.start);
|
|
15792
|
+
else if (cursor > drop.start) adjusted = drop.start + shift + Math.min(drop.label.length, cursor - drop.start);
|
|
15793
|
+
shift += drop.label.length - (drop.end - drop.start);
|
|
15794
|
+
previous = drop.end;
|
|
15795
|
+
}
|
|
15796
|
+
return { text: display + text.slice(previous), cursor: Math.max(0, adjusted) };
|
|
15797
|
+
}
|
|
15798
|
+
function hasDroppedImage(text) {
|
|
15799
|
+
return imageDrops(text).length > 0;
|
|
15800
|
+
}
|
|
15801
|
+
async function prepareImageInput(text) {
|
|
15802
|
+
const drops = imageDrops(text);
|
|
15803
|
+
if (!drops.length) return { text };
|
|
15804
|
+
if (drops.length > 1) throw new Error("Attach one image per message. Cosmic currently accepts one image in its active context.");
|
|
15805
|
+
const drop = drops[0];
|
|
15806
|
+
const file = await open9(drop.path, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
|
|
15807
|
+
let bytes;
|
|
15808
|
+
try {
|
|
15809
|
+
const stat11 = await file.stat();
|
|
15810
|
+
if (!stat11.isFile() || stat11.size > MAX_SOURCE_BYTES) throw new Error("Image must be a regular file no larger than 20 MiB.");
|
|
15811
|
+
const buffer = Buffer.alloc(Math.min(stat11.size, MAX_SOURCE_BYTES) + 1);
|
|
15812
|
+
let used = 0;
|
|
15813
|
+
while (used < buffer.length) {
|
|
15814
|
+
const read = await file.read(buffer, used, buffer.length - used, null);
|
|
15815
|
+
if (!read.bytesRead) break;
|
|
15816
|
+
used += read.bytesRead;
|
|
15817
|
+
}
|
|
15818
|
+
if (used > MAX_SOURCE_BYTES) throw new Error("Image exceeds 20 MiB.");
|
|
15819
|
+
bytes = buffer.subarray(0, used);
|
|
15820
|
+
} finally {
|
|
15821
|
+
await file.close();
|
|
15822
|
+
}
|
|
15823
|
+
const image = sharp(bytes, { limitInputPixels: 4e7, animated: false });
|
|
15824
|
+
const metadata = await image.metadata();
|
|
15825
|
+
if (!["png", "jpeg", "webp"].includes(metadata.format ?? "") || (metadata.pages ?? 1) > 1) throw new Error("Attach a single PNG, JPEG or WebP image.");
|
|
15826
|
+
const normalized = await image.rotate().resize({ width: 1024, height: 1024, fit: "inside", withoutEnlargement: true }).png().toBuffer();
|
|
15827
|
+
if (normalized.length > 5 * 1024 * 1024) throw new Error("Normalized image exceeds the upload limit.");
|
|
15828
|
+
return { text: formatImageInput(text).text, image: { url: `data:image/png;base64,${normalized.toString("base64")}` } };
|
|
15829
|
+
}
|
|
15830
|
+
|
|
15685
15831
|
// src/core/manual-compaction.ts
|
|
15686
15832
|
async function compactManually(messages, pinned, provider, model, focus = "", signal) {
|
|
15687
15833
|
const retained = new Set(pinned);
|
|
@@ -15959,7 +16105,8 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
15959
16105
|
async ask(input, handlers = {}) {
|
|
15960
16106
|
if (!this.started) await this.start();
|
|
15961
16107
|
if (!this.firstTask) this.firstTask = input;
|
|
15962
|
-
|
|
16108
|
+
if (handlers.image) for (const message of this.messages) delete message.image;
|
|
16109
|
+
const asked = { role: "user", content: input, ...handlers.image ? { image: handlers.image } : {} };
|
|
15963
16110
|
this.messages.push(asked);
|
|
15964
16111
|
this.pinnedMessages.push(asked);
|
|
15965
16112
|
this.userConstraints.push({ kind: "message", content: input });
|
|
@@ -16275,7 +16422,7 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
16275
16422
|
constraintVersion: 1,
|
|
16276
16423
|
userConstraints: [...this.userConstraints],
|
|
16277
16424
|
mcpResultArchiveVersion: 1,
|
|
16278
|
-
...this.messages.some(isCompactionSummary) ? { contextSnapshot: { version: 1, kind: "text", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16425
|
+
...this.messages.some((message) => isCompactionSummary(message) || message.image) ? { contextSnapshot: { version: 1, kind: "text", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16279
16426
|
// How the last turn ended, so a saved interactive transcript says whether
|
|
16280
16427
|
// its final message is an answer or the place it ran out of room - the
|
|
16281
16428
|
// same question `ultrac run` already answers for a one-shot run.
|
|
@@ -16490,7 +16637,10 @@ ${context}`
|
|
|
16490
16637
|
async ask(input, handlers = {}) {
|
|
16491
16638
|
if (!this.started) await this.start();
|
|
16492
16639
|
if (!this.firstTask) this.firstTask = input;
|
|
16493
|
-
|
|
16640
|
+
if (handlers.image) {
|
|
16641
|
+
for (const message of this.messages) if (message.role === "user" || message.role === "system") delete message.image;
|
|
16642
|
+
}
|
|
16643
|
+
const asked = { role: "user", content: input, ...handlers.image ? { image: handlers.image } : {} };
|
|
16494
16644
|
this.messages.push(asked);
|
|
16495
16645
|
this.pinnedMessages.push(asked);
|
|
16496
16646
|
this.userConstraints.push({ kind: "message", content: input });
|
|
@@ -16840,7 +16990,7 @@ ${guidance.join("\n")}` : ""}`;
|
|
|
16840
16990
|
constraintVersion: 1,
|
|
16841
16991
|
userConstraints: [...this.userConstraints],
|
|
16842
16992
|
mcpResultArchiveVersion: 1,
|
|
16843
|
-
...this.messages.some(isCompactionSummary) ? { contextSnapshot: { version: 1, kind: "native", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16993
|
+
...this.messages.some((message) => isCompactionSummary(message) || (message.role === "user" || message.role === "system") && message.image) ? { contextSnapshot: { version: 1, kind: "native", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16844
16994
|
// How the last turn ended, so a saved interactive transcript says whether
|
|
16845
16995
|
// its final message is an answer or the place it ran out of room - the
|
|
16846
16996
|
// same question `ultrac run` already answers for a one-shot run.
|
|
@@ -16882,10 +17032,10 @@ function boundedInteger(value, name, min, max) {
|
|
|
16882
17032
|
}
|
|
16883
17033
|
function abortableSleep(milliseconds, signal) {
|
|
16884
17034
|
signal.throwIfAborted();
|
|
16885
|
-
return new Promise((
|
|
17035
|
+
return new Promise((resolve17, reject) => {
|
|
16886
17036
|
const timer = setTimeout(() => {
|
|
16887
17037
|
signal.removeEventListener("abort", abort);
|
|
16888
|
-
|
|
17038
|
+
resolve17();
|
|
16889
17039
|
}, milliseconds);
|
|
16890
17040
|
const abort = () => {
|
|
16891
17041
|
clearTimeout(timer);
|
|
@@ -17271,53 +17421,8 @@ function attachProgressUi(options = {}) {
|
|
|
17271
17421
|
};
|
|
17272
17422
|
}
|
|
17273
17423
|
|
|
17274
|
-
// src/ui/
|
|
17275
|
-
import {
|
|
17276
|
-
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17277
|
-
var Spinner = class {
|
|
17278
|
-
constructor(label = "thinking") {
|
|
17279
|
-
this.label = label;
|
|
17280
|
-
}
|
|
17281
|
-
label;
|
|
17282
|
-
timer;
|
|
17283
|
-
index = 0;
|
|
17284
|
-
holdWhile;
|
|
17285
|
-
held = false;
|
|
17286
|
-
start(holdWhile) {
|
|
17287
|
-
this.holdWhile = holdWhile;
|
|
17288
|
-
if (!stdout4.isTTY || this.timer) return;
|
|
17289
|
-
this.render();
|
|
17290
|
-
this.timer = setInterval(() => this.render(), 90);
|
|
17291
|
-
}
|
|
17292
|
-
update(label) {
|
|
17293
|
-
this.label = label;
|
|
17294
|
-
}
|
|
17295
|
-
stop() {
|
|
17296
|
-
if (this.timer) {
|
|
17297
|
-
clearInterval(this.timer);
|
|
17298
|
-
this.timer = void 0;
|
|
17299
|
-
}
|
|
17300
|
-
if (stdout4.isTTY) {
|
|
17301
|
-
stdout4.clearLine(0);
|
|
17302
|
-
stdout4.cursorTo(0);
|
|
17303
|
-
}
|
|
17304
|
-
}
|
|
17305
|
-
render() {
|
|
17306
|
-
if (this.holdWhile?.()) {
|
|
17307
|
-
if (!this.held) {
|
|
17308
|
-
stdout4.clearLine(0);
|
|
17309
|
-
stdout4.cursorTo(0);
|
|
17310
|
-
this.held = true;
|
|
17311
|
-
}
|
|
17312
|
-
return;
|
|
17313
|
-
}
|
|
17314
|
-
this.held = false;
|
|
17315
|
-
this.index = (this.index + 1) % FRAMES.length;
|
|
17316
|
-
stdout4.clearLine(0);
|
|
17317
|
-
stdout4.cursorTo(0);
|
|
17318
|
-
stdout4.write(` ${FRAMES[this.index]} ${this.label}\u2026`);
|
|
17319
|
-
}
|
|
17320
|
-
};
|
|
17424
|
+
// src/ui/activity.ts
|
|
17425
|
+
import { stripVTControlCharacters } from "util";
|
|
17321
17426
|
|
|
17322
17427
|
// src/ui/tool-activity.ts
|
|
17323
17428
|
var VERBS = {
|
|
@@ -17390,14 +17495,129 @@ function humanize(value) {
|
|
|
17390
17495
|
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : "Tool";
|
|
17391
17496
|
}
|
|
17392
17497
|
|
|
17498
|
+
// src/ui/spinner.ts
|
|
17499
|
+
import { stdout as stdout4 } from "process";
|
|
17500
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17501
|
+
var Spinner = class {
|
|
17502
|
+
constructor(label = "thinking") {
|
|
17503
|
+
this.label = label;
|
|
17504
|
+
}
|
|
17505
|
+
label;
|
|
17506
|
+
timer;
|
|
17507
|
+
index = 0;
|
|
17508
|
+
holdWhile;
|
|
17509
|
+
held = false;
|
|
17510
|
+
startedAt;
|
|
17511
|
+
start(holdWhile, startedAt = performance.now()) {
|
|
17512
|
+
this.holdWhile = holdWhile;
|
|
17513
|
+
if (this.startedAt !== void 0) return;
|
|
17514
|
+
this.startedAt = startedAt;
|
|
17515
|
+
this.held = false;
|
|
17516
|
+
if (!stdout4.isTTY) return;
|
|
17517
|
+
this.render();
|
|
17518
|
+
this.timer = setInterval(() => this.render(), 90);
|
|
17519
|
+
}
|
|
17520
|
+
update(label) {
|
|
17521
|
+
this.label = label;
|
|
17522
|
+
}
|
|
17523
|
+
stop(completion) {
|
|
17524
|
+
const elapsed = this.startedAt === void 0 ? void 0 : performance.now() - this.startedAt;
|
|
17525
|
+
this.startedAt = void 0;
|
|
17526
|
+
if (this.timer) {
|
|
17527
|
+
clearInterval(this.timer);
|
|
17528
|
+
this.timer = void 0;
|
|
17529
|
+
}
|
|
17530
|
+
if (stdout4.isTTY && elapsed !== void 0) {
|
|
17531
|
+
stdout4.clearLine(0);
|
|
17532
|
+
stdout4.cursorTo(0);
|
|
17533
|
+
if (completion && elapsed >= 1e3) stdout4.write(` ${completion} ${formatSeconds(elapsed)}.
|
|
17534
|
+
`);
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
render() {
|
|
17538
|
+
if (this.holdWhile?.()) {
|
|
17539
|
+
if (!this.held) {
|
|
17540
|
+
stdout4.clearLine(0);
|
|
17541
|
+
stdout4.cursorTo(0);
|
|
17542
|
+
this.held = true;
|
|
17543
|
+
}
|
|
17544
|
+
return;
|
|
17545
|
+
}
|
|
17546
|
+
this.held = false;
|
|
17547
|
+
this.index = (this.index + 1) % FRAMES.length;
|
|
17548
|
+
stdout4.clearLine(0);
|
|
17549
|
+
stdout4.cursorTo(0);
|
|
17550
|
+
const milliseconds = performance.now() - (this.startedAt ?? performance.now());
|
|
17551
|
+
const elapsed = milliseconds >= 1e3 ? formatElapsed(milliseconds) : "";
|
|
17552
|
+
const limit = Math.max(0, (stdout4.columns ?? 80) - elapsed.length - 10);
|
|
17553
|
+
let label = "";
|
|
17554
|
+
let cells2 = 0;
|
|
17555
|
+
for (const character of this.label.replace(/[\x00-\x1f\x7f]/g, " ")) {
|
|
17556
|
+
const size = character.codePointAt(0) > 127 ? 2 : 1;
|
|
17557
|
+
if (cells2 + size > limit) break;
|
|
17558
|
+
label += character;
|
|
17559
|
+
cells2 += size;
|
|
17560
|
+
}
|
|
17561
|
+
stdout4.write(` ${FRAMES[this.index]} ${label}\u2026${elapsed ? ` \xB7 ${elapsed}` : ""}`);
|
|
17562
|
+
}
|
|
17563
|
+
};
|
|
17564
|
+
function formatSeconds(milliseconds) {
|
|
17565
|
+
const seconds = Math.max(0, milliseconds) / 1e3;
|
|
17566
|
+
const count3 = seconds.toFixed(1).replace(/\.0$/, "");
|
|
17567
|
+
return `${count3} ${count3 === "1" ? "second" : "seconds"}`;
|
|
17568
|
+
}
|
|
17569
|
+
|
|
17570
|
+
// src/ui/activity.ts
|
|
17571
|
+
var sink;
|
|
17572
|
+
function setActivitySink(value) {
|
|
17573
|
+
sink = value;
|
|
17574
|
+
}
|
|
17575
|
+
function getActivitySink() {
|
|
17576
|
+
return sink;
|
|
17577
|
+
}
|
|
17578
|
+
var clean = (text) => stripVTControlCharacters(text).replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
17579
|
+
function activityEntry(call, result, durationMs) {
|
|
17580
|
+
const fields = ["command", "script", "path", "paths", "query", "pattern", "url", "cwd"];
|
|
17581
|
+
const request = fields.flatMap((key) => {
|
|
17582
|
+
const value = call.input[key];
|
|
17583
|
+
return typeof value === "string" ? [`${key}: ${value}`] : Array.isArray(value) && value.every((item) => typeof item === "string") ? [`${key}: ${value.join(", ")}`] : [];
|
|
17584
|
+
}).join(" \xB7 ");
|
|
17585
|
+
return {
|
|
17586
|
+
label: clean(toolActivityLabel(call)),
|
|
17587
|
+
request: clean(request).slice(0, 4e3),
|
|
17588
|
+
output: clean(result.content).slice(0, 1e3),
|
|
17589
|
+
ok: result.ok,
|
|
17590
|
+
durationMs
|
|
17591
|
+
};
|
|
17592
|
+
}
|
|
17593
|
+
function activityRows(snapshot, expanded, selected, end) {
|
|
17594
|
+
const failures = snapshot.entries.filter((entry) => !entry.ok).length;
|
|
17595
|
+
const elapsed = snapshot.thinkingMs >= 1e3 ? ` \xB7 Thought for ${formatSeconds(snapshot.thinkingMs)}` : "";
|
|
17596
|
+
const summary = `${expanded ? "\u25BE" : "\u25B8"} \u25CF ${snapshot.entries.length} actions${failures ? ` \xB7 ${failures} failed` : ""}${snapshot.finished ? elapsed : ""} \xB7 Ctrl+O details`;
|
|
17597
|
+
const rows = [{ text: summary, ok: failures || snapshot.failed ? false : snapshot.finished ? true : void 0 }];
|
|
17598
|
+
if (!expanded) return rows;
|
|
17599
|
+
const last = Math.min(snapshot.entries.length, end);
|
|
17600
|
+
for (let index = Math.max(0, last - 5); index < last; index += 1) {
|
|
17601
|
+
const entry = snapshot.entries[index];
|
|
17602
|
+
const duration = entry.durationMs >= 1e3 ? ` \xB7 Worked for ${formatSeconds(entry.durationMs)}` : "";
|
|
17603
|
+
rows.push({ text: ` ${selected === index ? "\u25BE" : "\u25B8"} \u25CF ${entry.label}${duration}`, ok: entry.ok, entry: index });
|
|
17604
|
+
if (selected === index) {
|
|
17605
|
+
if (entry.request) rows.push({ text: ` ${entry.request}`, entry: index });
|
|
17606
|
+
if (entry.output) rows.push({ text: ` ${entry.output}`, ok: entry.ok, entry: index });
|
|
17607
|
+
}
|
|
17608
|
+
}
|
|
17609
|
+
if (snapshot.entries.length > 5) rows.push({ text: " Scroll to browse actions \xB7 /output for full saved results" });
|
|
17610
|
+
return rows;
|
|
17611
|
+
}
|
|
17612
|
+
|
|
17393
17613
|
// src/ui/interactive-choice.ts
|
|
17394
17614
|
var MOUSE_ON = "\x1B[?1006h\x1B[?1000h";
|
|
17395
17615
|
var MOUSE_OFF = "\x1B[?1000l\x1B[?1006l";
|
|
17396
17616
|
function displayLabel(value, width2) {
|
|
17397
|
-
const
|
|
17617
|
+
const clean2 = value.replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\)|.)/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
17398
17618
|
let result = "";
|
|
17399
17619
|
let used = 0;
|
|
17400
|
-
for (const character of
|
|
17620
|
+
for (const character of clean2) {
|
|
17401
17621
|
const size = /[\p{Mark}\u200d\ufe0e\ufe0f]/u.test(character) ? 0 : character.codePointAt(0) < 128 ? 1 : 2;
|
|
17402
17622
|
if (used + size > width2 - 1) return `${result}\u2026`;
|
|
17403
17623
|
result += character;
|
|
@@ -17410,8 +17630,8 @@ function attachInteractiveChoices(readline, options, settings = {}) {
|
|
|
17410
17630
|
const output = settings.output ?? rl.output;
|
|
17411
17631
|
const prompt = settings.prompt ?? " \u203A ";
|
|
17412
17632
|
let resolveReady;
|
|
17413
|
-
const ready = new Promise((
|
|
17414
|
-
resolveReady =
|
|
17633
|
+
const ready = new Promise((resolve17) => {
|
|
17634
|
+
resolveReady = resolve17;
|
|
17415
17635
|
});
|
|
17416
17636
|
const original = rl._ttyWrite;
|
|
17417
17637
|
const priorPrompt = rl.getPrompt();
|
|
@@ -17617,6 +17837,15 @@ function attachInteractiveChoices(readline, options, settings = {}) {
|
|
|
17617
17837
|
}
|
|
17618
17838
|
|
|
17619
17839
|
// src/ui/slash-completion.ts
|
|
17840
|
+
function builtinSlashEntries(commands, help) {
|
|
17841
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
17842
|
+
for (const line of help.split("\n")) {
|
|
17843
|
+
const match = /^(\/[^\n]+?)\s{2,}(.+)$/.exec(line.trim());
|
|
17844
|
+
if (!match) continue;
|
|
17845
|
+
for (const command of match[1].matchAll(/\/([a-z-]+)/g)) descriptions.set(command[1], match[2]);
|
|
17846
|
+
}
|
|
17847
|
+
return commands.map((command) => ({ text: `/${command}`, label: `/${command}`, description: descriptions.get(command) }));
|
|
17848
|
+
}
|
|
17620
17849
|
function attachSlashCompletion(readline, loadEntries, render, initialEntries = []) {
|
|
17621
17850
|
const rl = readline;
|
|
17622
17851
|
const original = rl._ttyWrite;
|
|
@@ -17718,11 +17947,11 @@ function attachSlashCompletion(readline, loadEntries, render, initialEntries = [
|
|
|
17718
17947
|
|
|
17719
17948
|
// src/ui/terminal-layout.ts
|
|
17720
17949
|
import { Writable } from "stream";
|
|
17721
|
-
import { stripVTControlCharacters } from "util";
|
|
17950
|
+
import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
17722
17951
|
var CSI = "\x1B[";
|
|
17723
17952
|
var RESET_REGION = `${CSI}r`;
|
|
17724
17953
|
var MOUSE_OFF2 = `${CSI}?1000l${CSI}?1002l${CSI}?1003l${CSI}?1006l`;
|
|
17725
|
-
var BASE_FOOTER_ROWS =
|
|
17954
|
+
var BASE_FOOTER_ROWS = 4;
|
|
17726
17955
|
var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
17727
17956
|
var cells = (value) => [...segmenter.segment(value)].map(({ segment }) => {
|
|
17728
17957
|
const code = segment.codePointAt(0) ?? 0;
|
|
@@ -17731,7 +17960,7 @@ var cells = (value) => [...segmenter.segment(value)].map(({ segment }) => {
|
|
|
17731
17960
|
});
|
|
17732
17961
|
var width = (text) => cells(text).reduce((sum, cell) => sum + cell.width, 0);
|
|
17733
17962
|
function plain(value) {
|
|
17734
|
-
return
|
|
17963
|
+
return stripVTControlCharacters2(value).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
17735
17964
|
}
|
|
17736
17965
|
function fit(text, columns) {
|
|
17737
17966
|
if (columns <= 0) return "";
|
|
@@ -17745,7 +17974,6 @@ function fit(text, columns) {
|
|
|
17745
17974
|
return result;
|
|
17746
17975
|
}
|
|
17747
17976
|
var cup = (row, column = 1) => `${CSI}${row};${column}H`;
|
|
17748
|
-
var count3 = (value) => value !== void 0 && Number.isFinite(value) && value >= 0 ? Math.floor(value).toLocaleString("en-US") : "unknown";
|
|
17749
17977
|
var ComposerOutput = class extends Writable {
|
|
17750
17978
|
constructor(layout) {
|
|
17751
17979
|
super();
|
|
@@ -17781,6 +18009,7 @@ var TerminalLayout = class {
|
|
|
17781
18009
|
details;
|
|
17782
18010
|
rl;
|
|
17783
18011
|
prompt = "\u203A ";
|
|
18012
|
+
inputFormatter;
|
|
17784
18013
|
transcriptRow = 1;
|
|
17785
18014
|
transcriptColumn = 1;
|
|
17786
18015
|
wrapPending = false;
|
|
@@ -17788,6 +18017,11 @@ var TerminalLayout = class {
|
|
|
17788
18017
|
lastFooterTop = 0;
|
|
17789
18018
|
suggestions = [];
|
|
17790
18019
|
selected = 0;
|
|
18020
|
+
activity;
|
|
18021
|
+
activityExpanded = false;
|
|
18022
|
+
activitySelected = -1;
|
|
18023
|
+
activityEnd = 0;
|
|
18024
|
+
restoreActivityInput;
|
|
17791
18025
|
installed = false;
|
|
17792
18026
|
closed = false;
|
|
17793
18027
|
suspended = 0;
|
|
@@ -17825,11 +18059,15 @@ var TerminalLayout = class {
|
|
|
17825
18059
|
get suggestionRows() {
|
|
17826
18060
|
return Math.min(8, this.suggestions.length, Math.max(0, this.rows - BASE_FOOTER_ROWS - 2));
|
|
17827
18061
|
}
|
|
18062
|
+
get activityLines() {
|
|
18063
|
+
if (!this.activity) return [];
|
|
18064
|
+
return activityRows(this.activity, this.activityExpanded, this.activitySelected, this.activityEnd).slice(0, Math.max(1, Math.min(10, this.rows - BASE_FOOTER_ROWS - this.suggestionRows - 2)));
|
|
18065
|
+
}
|
|
17828
18066
|
get contentRows() {
|
|
17829
|
-
return this.rows - BASE_FOOTER_ROWS - this.suggestionRows;
|
|
18067
|
+
return this.rows - BASE_FOOTER_ROWS - this.suggestionRows - this.activityLines.length;
|
|
17830
18068
|
}
|
|
17831
18069
|
get inputRow() {
|
|
17832
|
-
return this.rows -
|
|
18070
|
+
return this.rows - 2;
|
|
17833
18071
|
}
|
|
17834
18072
|
install() {
|
|
17835
18073
|
if (this.installed || this.closed || !this.enabled) return this;
|
|
@@ -17851,6 +18089,93 @@ var TerminalLayout = class {
|
|
|
17851
18089
|
attachReadline(rl) {
|
|
17852
18090
|
this.rl = rl;
|
|
17853
18091
|
rl.setPrompt(this.prompt);
|
|
18092
|
+
const reader = rl;
|
|
18093
|
+
const original = reader._ttyWrite;
|
|
18094
|
+
let sequence = "";
|
|
18095
|
+
let sequenceTimer;
|
|
18096
|
+
const wrapper = (text, key) => {
|
|
18097
|
+
if (this.activity && !this.suspended && key?.ctrl && key.name === "o") {
|
|
18098
|
+
this.toggleActivity();
|
|
18099
|
+
return;
|
|
18100
|
+
}
|
|
18101
|
+
const value = key?.sequence ?? text ?? "";
|
|
18102
|
+
if (sequence || value.startsWith("\x1B[<")) {
|
|
18103
|
+
sequence += value;
|
|
18104
|
+
clearTimeout(sequenceTimer);
|
|
18105
|
+
const mouse = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(sequence);
|
|
18106
|
+
if (mouse) {
|
|
18107
|
+
sequence = "";
|
|
18108
|
+
if (!this.suspended) this.activityMouse(Number(mouse[1]), Number(mouse[3]), mouse[4] === "M");
|
|
18109
|
+
return;
|
|
18110
|
+
}
|
|
18111
|
+
if (/^\x1b\[<[0-9;]*$/.test(sequence) && sequence.length < 64) {
|
|
18112
|
+
sequenceTimer = setTimeout(() => {
|
|
18113
|
+
sequence = "";
|
|
18114
|
+
}, 250);
|
|
18115
|
+
return;
|
|
18116
|
+
}
|
|
18117
|
+
const pending = sequence;
|
|
18118
|
+
sequence = "";
|
|
18119
|
+
original.call(reader, pending, key);
|
|
18120
|
+
return;
|
|
18121
|
+
}
|
|
18122
|
+
original.call(reader, text, key);
|
|
18123
|
+
};
|
|
18124
|
+
reader._ttyWrite = wrapper;
|
|
18125
|
+
this.restoreActivityInput = () => {
|
|
18126
|
+
clearTimeout(sequenceTimer);
|
|
18127
|
+
if (reader._ttyWrite === wrapper) reader._ttyWrite = original;
|
|
18128
|
+
};
|
|
18129
|
+
this.paintFooter();
|
|
18130
|
+
}
|
|
18131
|
+
setActivity(snapshot) {
|
|
18132
|
+
this.clearPreviousFooter();
|
|
18133
|
+
const previous = this.activity?.entries.length ?? 0;
|
|
18134
|
+
if (!snapshot.entries.length && !snapshot.finished) {
|
|
18135
|
+
this.activityExpanded = false;
|
|
18136
|
+
this.activitySelected = -1;
|
|
18137
|
+
}
|
|
18138
|
+
this.activity = snapshot;
|
|
18139
|
+
if (this.activityEnd >= previous || !previous) this.activityEnd = snapshot.entries.length;
|
|
18140
|
+
this.reflowActivity();
|
|
18141
|
+
}
|
|
18142
|
+
toggleActivity() {
|
|
18143
|
+
if (!this.activity) return;
|
|
18144
|
+
this.clearPreviousFooter();
|
|
18145
|
+
this.activityExpanded = !this.activityExpanded;
|
|
18146
|
+
this.reflowActivity();
|
|
18147
|
+
}
|
|
18148
|
+
reflowActivity() {
|
|
18149
|
+
if (!this.enabled || !this.installed || this.closed) return;
|
|
18150
|
+
this.transcriptRow = Math.min(this.transcriptRow, this.contentRows);
|
|
18151
|
+
this.rawWrite(`${CSI}1;${this.contentRows}r`);
|
|
18152
|
+
this.paintFooter();
|
|
18153
|
+
}
|
|
18154
|
+
activityMouse(button, row, press) {
|
|
18155
|
+
if (!this.activity || !press) return;
|
|
18156
|
+
if (button === 64 || button === 65) {
|
|
18157
|
+
if (!this.activityExpanded) return;
|
|
18158
|
+
this.clearPreviousFooter();
|
|
18159
|
+
this.activityEnd = Math.max(Math.min(5, this.activity.entries.length), Math.min(this.activity.entries.length, this.activityEnd + (button === 64 ? -1 : 1)));
|
|
18160
|
+
this.activitySelected = -1;
|
|
18161
|
+
this.reflowActivity();
|
|
18162
|
+
return;
|
|
18163
|
+
}
|
|
18164
|
+
if (button !== 0) return;
|
|
18165
|
+
const index = row - this.contentRows - 1;
|
|
18166
|
+
if (index === 0) {
|
|
18167
|
+
this.toggleActivity();
|
|
18168
|
+
return;
|
|
18169
|
+
}
|
|
18170
|
+
const entry = this.activityLines[index]?.entry;
|
|
18171
|
+
if (entry !== void 0) {
|
|
18172
|
+
this.clearPreviousFooter();
|
|
18173
|
+
this.activitySelected = this.activitySelected === entry ? -1 : entry;
|
|
18174
|
+
this.reflowActivity();
|
|
18175
|
+
}
|
|
18176
|
+
}
|
|
18177
|
+
setInputFormatter(formatter) {
|
|
18178
|
+
this.inputFormatter = formatter;
|
|
17854
18179
|
this.paintFooter();
|
|
17855
18180
|
}
|
|
17856
18181
|
setPrompt(prompt) {
|
|
@@ -17910,7 +18235,7 @@ var TerminalLayout = class {
|
|
|
17910
18235
|
for (let row = first; row <= last; row += 1) erased += `${cup(row)}${CSI}2K`;
|
|
17911
18236
|
return erased + saved;
|
|
17912
18237
|
});
|
|
17913
|
-
if (this.wrapPending && !/^[\r\n]/.test(
|
|
18238
|
+
if (this.wrapPending && !/^[\r\n]/.test(stripVTControlCharacters2(formatted))) {
|
|
17914
18239
|
this.rawWrite(`${cup(this.transcriptRow, this.columns)}\r
|
|
17915
18240
|
`);
|
|
17916
18241
|
this.transcriptRow = Math.min(this.contentRows, this.transcriptRow + 1);
|
|
@@ -17987,8 +18312,10 @@ var TerminalLayout = class {
|
|
|
17987
18312
|
composer() {
|
|
17988
18313
|
const available = this.columns - 4;
|
|
17989
18314
|
const prompt = fit(this.rl?.getPrompt() ?? this.prompt, Math.min(24, Math.max(2, Math.floor(available / 3))));
|
|
17990
|
-
const
|
|
17991
|
-
const
|
|
18315
|
+
const raw = this.rl?.line ?? "";
|
|
18316
|
+
const formatted = this.inputFormatter?.(raw, this.rl?.cursor ?? 0) ?? { text: raw, cursor: this.rl?.cursor ?? 0 };
|
|
18317
|
+
const input = plain(formatted.text);
|
|
18318
|
+
const cursor = width(plain(formatted.text.slice(0, formatted.cursor)));
|
|
17992
18319
|
const capacity = Math.max(1, available - width(prompt));
|
|
17993
18320
|
const offset = Math.max(0, cursor - capacity + 1);
|
|
17994
18321
|
let visible = "";
|
|
@@ -18021,29 +18348,48 @@ var TerminalLayout = class {
|
|
|
18021
18348
|
const inner = this.columns - 2;
|
|
18022
18349
|
const border = "\u2500".repeat(inner);
|
|
18023
18350
|
const composer = this.composer();
|
|
18024
|
-
const
|
|
18025
|
-
const
|
|
18026
|
-
const metadata = `${status}${this.details.provider} \xB7 ${fit(this.details.model, modelRoom)} \xB7 ${this.details.mode}`;
|
|
18027
|
-
const usage = `${count3(this.details.tokens)} reported tokens \xB7 ${count3(this.details.steps)} steps \xB7 est ctx ${count3(this.details.contextUsed)}/${count3(this.details.contextMax)}`;
|
|
18351
|
+
const model = /^cosmic[- ]1$/i.test(this.details.model) ? "Cosmic 1" : this.details.model;
|
|
18352
|
+
const metadata = fit(model, this.columns);
|
|
18028
18353
|
let frame = "";
|
|
18354
|
+
const activity = this.activityLines;
|
|
18355
|
+
if (activity.length && !this.suspended) frame += `${CSI}?1006h${CSI}?1000h`;
|
|
18356
|
+
activity.forEach((row, index) => {
|
|
18357
|
+
const text = fit(row.text, this.columns);
|
|
18358
|
+
const color = process.env.NO_COLOR || row.ok === void 0 ? "" : `${CSI}${row.ok ? 32 : 31}m`;
|
|
18359
|
+
const dot = text.indexOf("\u25CF");
|
|
18360
|
+
const rendered = dot >= 0 && color ? `${text.slice(0, dot)}${color}\u25CF${CSI}39m${text.slice(dot + 1)}` : text;
|
|
18361
|
+
frame += `${cup(this.contentRows + 1 + index)}${CSI}0m${CSI}2K${rendered}`;
|
|
18362
|
+
});
|
|
18029
18363
|
const suggestions = this.suggestionRows;
|
|
18030
18364
|
const first = Math.max(0, Math.min(this.selected - Math.floor(suggestions / 2), this.suggestions.length - suggestions));
|
|
18365
|
+
const commandWidth = Math.min(
|
|
18366
|
+
Math.max(18, ...this.suggestions.map((row) => width(plain(row).split(/ {2,}/)[0] ?? ""))),
|
|
18367
|
+
Math.max(8, Math.floor(this.columns / 3))
|
|
18368
|
+
);
|
|
18031
18369
|
for (let i = 0; i < suggestions; i += 1) {
|
|
18032
18370
|
const index = first + i;
|
|
18033
18371
|
const selected = index === this.selected;
|
|
18034
|
-
|
|
18372
|
+
const entry = plain(this.suggestions[index] ?? "");
|
|
18373
|
+
const match = /^(.+?) {2,}(.+)$/.exec(entry);
|
|
18374
|
+
const command = fit(match?.[1] ?? entry, commandWidth);
|
|
18375
|
+
const description = match?.[2] ?? "";
|
|
18376
|
+
const row = fit(`${selected ? "\u203A" : " "} ${command}${description ? `${" ".repeat(Math.max(0, commandWidth - width(command)) + 6)}${description}` : ""}`, this.columns);
|
|
18377
|
+
const style = process.env.NO_COLOR ? "" : selected ? `${CSI}1m${CSI}39m${CSI}49m` : `${CSI}22m${CSI}39m${CSI}49m`;
|
|
18378
|
+
frame += `${cup(this.contentRows + activity.length + 1 + i)}${CSI}0m${CSI}2K${style}${row}${" ".repeat(this.columns - width(row))}${CSI}0m`;
|
|
18035
18379
|
}
|
|
18036
18380
|
const inputText = fit(composer.text, this.columns - 4);
|
|
18037
18381
|
const paddedInput = inputText + " ".repeat(Math.max(0, this.columns - 4 - width(inputText)));
|
|
18038
|
-
const
|
|
18382
|
+
const dimBorder = (value) => process.env.NO_COLOR ? value : `${CSI}90m${value}${CSI}39m`;
|
|
18383
|
+
const lines = [dimBorder(`\u250C${border}\u2510`), `${dimBorder("\u2502")} ${paddedInput} ${dimBorder("\u2502")}`, dimBorder(`\u2514${border}\u2518`), metadata];
|
|
18039
18384
|
lines.forEach((line, index) => {
|
|
18040
|
-
frame += `${cup(this.rows -
|
|
18385
|
+
frame += `${cup(this.rows - 3 + index)}${CSI}2K${line}`;
|
|
18041
18386
|
});
|
|
18042
18387
|
this.rawWrite(`${frame}${cup(this.inputRow, composer.column)}${CSI}?25h`);
|
|
18043
18388
|
}
|
|
18044
18389
|
close() {
|
|
18045
18390
|
if (this.closed) return;
|
|
18046
18391
|
this.closed = true;
|
|
18392
|
+
this.restoreActivityInput?.();
|
|
18047
18393
|
if (!this.installed) return;
|
|
18048
18394
|
this.output.write = this.originalWrite;
|
|
18049
18395
|
if (this.errorOutput && this.errorOutput !== this.output && this.originalErrorWrite) this.errorOutput.write = this.originalErrorWrite;
|
|
@@ -18068,6 +18414,26 @@ function createTerminalLayout(options) {
|
|
|
18068
18414
|
|
|
18069
18415
|
// src/ui/welcome.ts
|
|
18070
18416
|
import chalk10, { Chalk } from "chalk";
|
|
18417
|
+
import { homedir as homedir3 } from "os";
|
|
18418
|
+
import { sep } from "path";
|
|
18419
|
+
|
|
18420
|
+
// src/ui/provider-brand.ts
|
|
18421
|
+
import { stripVTControlCharacters as stripVTControlCharacters3 } from "util";
|
|
18422
|
+
var GOLD = "\x1B[38;2;202;168;103m";
|
|
18423
|
+
var DEFAULT_FOREGROUND = "\x1B[39m";
|
|
18424
|
+
function safeDisplayText(value) {
|
|
18425
|
+
return stripVTControlCharacters3(value).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
18426
|
+
}
|
|
18427
|
+
function providerDisplayName(provider) {
|
|
18428
|
+
const name = safeDisplayText(provider);
|
|
18429
|
+
return name.toLowerCase() === "ultrac" ? "spinalfluid" : name;
|
|
18430
|
+
}
|
|
18431
|
+
function paintProviderSpan(line, offset, provider, color) {
|
|
18432
|
+
if (!color || provider !== "spinalfluid" || offset < 0 || line.slice(offset, offset + provider.length) !== provider) return line;
|
|
18433
|
+
return `${line.slice(0, offset)}${GOLD}${provider}${DEFAULT_FOREGROUND}${line.slice(offset + provider.length)}`;
|
|
18434
|
+
}
|
|
18435
|
+
|
|
18436
|
+
// src/ui/welcome.ts
|
|
18071
18437
|
var LOGO = [
|
|
18072
18438
|
"\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
18073
18439
|
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
@@ -18079,8 +18445,7 @@ var LOGO = [
|
|
|
18079
18445
|
function renderWelcome(info, options = {}) {
|
|
18080
18446
|
const columns = normalizeColumns(options.columns);
|
|
18081
18447
|
const paint = new Chalk({ level: options.color === false ? 0 : chalk10.level });
|
|
18082
|
-
const
|
|
18083
|
-
const innerWidth = cardWidth - 4;
|
|
18448
|
+
const innerWidth = columns - 2;
|
|
18084
18449
|
const wide = columns >= 64;
|
|
18085
18450
|
const lines = [];
|
|
18086
18451
|
if (wide) {
|
|
@@ -18091,16 +18456,14 @@ function renderWelcome(info, options = {}) {
|
|
|
18091
18456
|
} else {
|
|
18092
18457
|
lines.push("", ` ${paint.bold.white("ULTRAC")} ${paint.gray(`v${info.version}`)}`);
|
|
18093
18458
|
}
|
|
18094
|
-
const
|
|
18095
|
-
const
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
lines.push(
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
lines.push(
|
|
18102
|
-
lines.push(cardLine(`logs ${shortenMiddle(info.diagnosticPath, Math.max(8, innerWidth - 5))}`, innerWidth, paint.gray));
|
|
18103
|
-
lines.push(paint.gray(`\u2570${"\u2500".repeat(cardWidth - 2)}\u256F`));
|
|
18459
|
+
const provider = providerDisplayName(info.providerName);
|
|
18460
|
+
const prefix = `${safeDisplayText(info.model)} \xB7 `;
|
|
18461
|
+
const account = `${provider}${info.planName ? ` ${safeDisplayText(info.planName)}` : ""}`;
|
|
18462
|
+
const modelLine = ` ${truncate3(`${prefix}${account}`, innerWidth)}`;
|
|
18463
|
+
lines.push("", paintProviderSpan(modelLine, 2 + prefix.length, provider, paint.level > 0));
|
|
18464
|
+
const home = homedir3();
|
|
18465
|
+
const workspace = info.workspace === home ? "~" : info.workspace.startsWith(`${home}${sep}`) ? `~${info.workspace.slice(home.length)}` : info.workspace;
|
|
18466
|
+
lines.push(paint.gray(` ${shortenMiddle(safeDisplayText(workspace), innerWidth)}`), "");
|
|
18104
18467
|
lines.push(
|
|
18105
18468
|
wide ? `${paint.gray(" /help commands")} ${paint.dim("\xB7")} ${paint.gray("Esc stop")} ${paint.dim("\xB7")} ${paint.gray("Ctrl-C exit")}` : paint.gray(" /help \xB7 Esc \xB7 Ctrl-C")
|
|
18106
18469
|
);
|
|
@@ -18118,10 +18481,6 @@ ${paint.gray("\u2502")}`,
|
|
|
18118
18481
|
prompt: paint.bold.white("\u2570\u2500\u276F ")
|
|
18119
18482
|
};
|
|
18120
18483
|
}
|
|
18121
|
-
function cardLine(value, width2, style) {
|
|
18122
|
-
const clipped = truncate3(value, width2);
|
|
18123
|
-
return `\u2502 ${style(clipped)}${" ".repeat(Math.max(0, width2 - clipped.length))} \u2502`;
|
|
18124
|
-
}
|
|
18125
18484
|
function normalizeColumns(columns) {
|
|
18126
18485
|
return Number.isFinite(columns) ? Math.max(16, Math.floor(columns)) : 80;
|
|
18127
18486
|
}
|
|
@@ -18137,11 +18496,18 @@ function shortenMiddle(value, width2) {
|
|
|
18137
18496
|
return `${value.slice(0, side)}\u2026${value.slice(value.length - (width2 - side - 1))}`;
|
|
18138
18497
|
}
|
|
18139
18498
|
|
|
18499
|
+
// src/ui/update-prompt.ts
|
|
18500
|
+
import { constants as constants7 } from "fs";
|
|
18501
|
+
import { lstat as lstat4, mkdir as mkdir15, open as open10, rename as rename4, unlink as unlink5 } from "fs/promises";
|
|
18502
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
18503
|
+
import { homedir as homedir4 } from "os";
|
|
18504
|
+
import { join as join15, parse, resolve as resolve13 } from "path";
|
|
18505
|
+
|
|
18140
18506
|
// src/update/check.ts
|
|
18141
18507
|
import { spawn as spawn4 } from "child_process";
|
|
18142
18508
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
18143
18509
|
import { readFile as readFile13, rename as rename3, unlink as unlink4, writeFile as writeFile12 } from "fs/promises";
|
|
18144
|
-
import { join as join14 } from "path";
|
|
18510
|
+
import { isAbsolute as isAbsolute5, join as join14, relative as relative2, resolve as resolve12 } from "path";
|
|
18145
18511
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
18146
18512
|
var QUERY_TIMEOUT_MS = 5e3;
|
|
18147
18513
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -18174,17 +18540,67 @@ async function queryLatestVersion(packageName) {
|
|
|
18174
18540
|
return void 0;
|
|
18175
18541
|
}
|
|
18176
18542
|
}
|
|
18177
|
-
async function installLatestVersion(packageName) {
|
|
18543
|
+
async function installLatestVersion(packageName, version = "latest") {
|
|
18544
|
+
if (version !== "latest" && !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
18545
|
+
throw new Error("Invalid npm release version.");
|
|
18546
|
+
}
|
|
18178
18547
|
const result = await runNpm(
|
|
18179
|
-
["install", "--global", `${packageName}
|
|
18548
|
+
["install", "--global", `${packageName}@${version}`, "--no-audit", "--no-fund"],
|
|
18180
18549
|
{ timeoutMs: INSTALL_TIMEOUT_MS, capture: false }
|
|
18181
18550
|
);
|
|
18182
18551
|
if (result.code !== 0) {
|
|
18183
18552
|
throw new Error(
|
|
18184
|
-
`npm could not install ${packageName}
|
|
18553
|
+
`npm could not install ${packageName}@${version}. Confirm npm can reach the registry and install global packages.`
|
|
18185
18554
|
);
|
|
18186
18555
|
}
|
|
18187
18556
|
}
|
|
18557
|
+
async function installVerifiedVersion(packageName, version) {
|
|
18558
|
+
await installLatestVersion(packageName, version);
|
|
18559
|
+
const result = await runNpm(["list", "--global", packageName, "--depth=0", "--json"], {
|
|
18560
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18561
|
+
capture: true,
|
|
18562
|
+
maxOutputBytes: 4096
|
|
18563
|
+
});
|
|
18564
|
+
let installedVersion;
|
|
18565
|
+
try {
|
|
18566
|
+
const parsed = JSON.parse(result.stdout);
|
|
18567
|
+
if (typeof parsed === "object" && parsed !== null && "dependencies" in parsed && typeof parsed.dependencies === "object" && parsed.dependencies !== null) {
|
|
18568
|
+
const dependency = parsed.dependencies[packageName];
|
|
18569
|
+
if (typeof dependency === "object" && dependency !== null && "version" in dependency) {
|
|
18570
|
+
installedVersion = dependency.version;
|
|
18571
|
+
}
|
|
18572
|
+
}
|
|
18573
|
+
} catch {
|
|
18574
|
+
}
|
|
18575
|
+
if (result.code !== 0 || installedVersion !== version) {
|
|
18576
|
+
throw new Error("The update could not be verified. Run ultrac update and restart Ultrac.");
|
|
18577
|
+
}
|
|
18578
|
+
try {
|
|
18579
|
+
const globalRoot = await runNpm(["root", "--global"], {
|
|
18580
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18581
|
+
capture: true,
|
|
18582
|
+
maxOutputBytes: 4096
|
|
18583
|
+
});
|
|
18584
|
+
const root = globalRoot.stdout.trim();
|
|
18585
|
+
if (globalRoot.code !== 0 || !isAbsolute5(root) || root.includes("\n")) throw new Error("Invalid npm root");
|
|
18586
|
+
const packageRoot = join14(root, packageName);
|
|
18587
|
+
const manifest = JSON.parse(await readFile13(join14(packageRoot, "package.json"), "utf8"));
|
|
18588
|
+
if (typeof manifest !== "object" || manifest === null || !("version" in manifest) || manifest.version !== version || !("bin" in manifest)) throw new Error("Invalid package");
|
|
18589
|
+
const bin = manifest.bin;
|
|
18590
|
+
const entry = typeof bin === "string" ? bin : typeof bin === "object" && bin !== null && "ultrac" in bin ? bin.ultrac : void 0;
|
|
18591
|
+
if (typeof entry !== "string") throw new Error("Missing executable");
|
|
18592
|
+
const executable = resolve12(packageRoot, entry);
|
|
18593
|
+
const withinPackage = relative2(packageRoot, executable);
|
|
18594
|
+
if (withinPackage.startsWith("..") || isAbsolute5(withinPackage)) throw new Error("Invalid executable");
|
|
18595
|
+
const probe = await runExecutable(process.execPath, [executable, "--version"], {
|
|
18596
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18597
|
+
capture: true
|
|
18598
|
+
});
|
|
18599
|
+
if (probe.code !== 0 || probe.stdout.trim() !== version) throw new Error("Invalid executable version");
|
|
18600
|
+
} catch {
|
|
18601
|
+
throw new Error("The installed Ultrac executable could not be verified. Restart and run ultrac --version.");
|
|
18602
|
+
}
|
|
18603
|
+
}
|
|
18188
18604
|
function isNewerVersion(candidate, current) {
|
|
18189
18605
|
const left = parseVersion(candidate);
|
|
18190
18606
|
const right = parseVersion(current);
|
|
@@ -18237,12 +18653,15 @@ async function writeCache(path, cache) {
|
|
|
18237
18653
|
}
|
|
18238
18654
|
}
|
|
18239
18655
|
async function runNpm(args, options) {
|
|
18656
|
+
return runExecutable(process.platform === "win32" ? "npm.cmd" : "npm", args, options);
|
|
18657
|
+
}
|
|
18658
|
+
async function runExecutable(executable, args, options) {
|
|
18240
18659
|
const env2 = { ...process.env };
|
|
18241
18660
|
for (const key of Object.keys(env2)) {
|
|
18242
18661
|
if (key.startsWith("ULTRAC_")) delete env2[key];
|
|
18243
18662
|
}
|
|
18244
|
-
return new Promise((
|
|
18245
|
-
const child = spawn4(
|
|
18663
|
+
return new Promise((resolve17) => {
|
|
18664
|
+
const child = spawn4(executable, args, {
|
|
18246
18665
|
env: env2,
|
|
18247
18666
|
stdio: options.capture ? ["ignore", "pipe", "ignore"] : "inherit",
|
|
18248
18667
|
windowsHide: true
|
|
@@ -18251,21 +18670,90 @@ async function runNpm(args, options) {
|
|
|
18251
18670
|
if (options.capture) {
|
|
18252
18671
|
child.stdout?.setEncoding("utf8");
|
|
18253
18672
|
child.stdout?.on("data", (chunk) => {
|
|
18254
|
-
|
|
18673
|
+
const limit = options.maxOutputBytes ?? 256;
|
|
18674
|
+
if (stdout6.length <= limit) stdout6 += chunk.slice(0, limit + 1 - stdout6.length);
|
|
18255
18675
|
});
|
|
18256
18676
|
}
|
|
18257
18677
|
const timer = setTimeout(() => child.kill(), options.timeoutMs);
|
|
18258
18678
|
child.once("error", () => {
|
|
18259
18679
|
clearTimeout(timer);
|
|
18260
|
-
|
|
18680
|
+
resolve17({ code: 1, stdout: "" });
|
|
18261
18681
|
});
|
|
18262
18682
|
child.once("close", (code) => {
|
|
18263
18683
|
clearTimeout(timer);
|
|
18264
|
-
|
|
18684
|
+
resolve17({ code: code ?? 1, stdout: stdout6 });
|
|
18265
18685
|
});
|
|
18266
18686
|
});
|
|
18267
18687
|
}
|
|
18268
18688
|
|
|
18689
|
+
// src/ui/update-prompt.ts
|
|
18690
|
+
var UPDATE_CHOICES = ["Update now", "Skip", "Skip until next version"];
|
|
18691
|
+
async function offerUpdatePrompt(status, options) {
|
|
18692
|
+
const version = status.latestVersion;
|
|
18693
|
+
if (!options.interactive || options.signal?.aborted || !status.updateAvailable || !version || !validVersion(version) || !validVersion(status.currentVersion) || !isNewerVersion(version, status.currentVersion) || await skippedVersion() === version) return "not_offered";
|
|
18694
|
+
const answer = await options.ask(
|
|
18695
|
+
`Ultrac ${version} is available. You are running ${status.currentVersion}.`,
|
|
18696
|
+
UPDATE_CHOICES,
|
|
18697
|
+
options.signal
|
|
18698
|
+
);
|
|
18699
|
+
if (options.signal?.aborted) return "skip";
|
|
18700
|
+
if (answer === UPDATE_CHOICES[0]) return "update_now";
|
|
18701
|
+
if (answer === UPDATE_CHOICES[2]) {
|
|
18702
|
+
await rememberSkippedVersion(version);
|
|
18703
|
+
return "skip_version";
|
|
18704
|
+
}
|
|
18705
|
+
return "skip";
|
|
18706
|
+
}
|
|
18707
|
+
function validVersion(version) {
|
|
18708
|
+
return version.length <= 128 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
|
|
18709
|
+
}
|
|
18710
|
+
async function statePath(create) {
|
|
18711
|
+
const home = resolve13(ultracHomeDir());
|
|
18712
|
+
if (home === parse(home).root || home === resolve13(homedir4())) {
|
|
18713
|
+
throw new Error("Ultrac update preferences require a dedicated ULTRAC_HOME directory.");
|
|
18714
|
+
}
|
|
18715
|
+
if (create) await mkdir15(home, { recursive: true, mode: 448 });
|
|
18716
|
+
const info = await lstat4(home);
|
|
18717
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
18718
|
+
throw new Error("Ultrac update preferences require a real home directory.");
|
|
18719
|
+
}
|
|
18720
|
+
return join15(home, "update-preferences.json");
|
|
18721
|
+
}
|
|
18722
|
+
async function skippedVersion() {
|
|
18723
|
+
try {
|
|
18724
|
+
const path = await statePath(false);
|
|
18725
|
+
const file = await open10(path, constants7.O_RDONLY | constants7.O_NOFOLLOW);
|
|
18726
|
+
try {
|
|
18727
|
+
const info = await file.stat();
|
|
18728
|
+
if (!info.isFile() || info.size > 1024) return void 0;
|
|
18729
|
+
const parsed = JSON.parse(await file.readFile("utf8"));
|
|
18730
|
+
if (typeof parsed !== "object" || parsed === null || !("skippedVersion" in parsed)) return void 0;
|
|
18731
|
+
return typeof parsed.skippedVersion === "string" && validVersion(parsed.skippedVersion) ? parsed.skippedVersion : void 0;
|
|
18732
|
+
} finally {
|
|
18733
|
+
await file.close();
|
|
18734
|
+
}
|
|
18735
|
+
} catch {
|
|
18736
|
+
return void 0;
|
|
18737
|
+
}
|
|
18738
|
+
}
|
|
18739
|
+
async function rememberSkippedVersion(version) {
|
|
18740
|
+
const path = await statePath(true);
|
|
18741
|
+
const temporary = `${path}.${randomBytes5(8).toString("hex")}.tmp`;
|
|
18742
|
+
try {
|
|
18743
|
+
const file = await open10(temporary, constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL, 384);
|
|
18744
|
+
try {
|
|
18745
|
+
await file.writeFile(`${JSON.stringify({ skippedVersion: version })}
|
|
18746
|
+
`, "utf8");
|
|
18747
|
+
await file.sync();
|
|
18748
|
+
} finally {
|
|
18749
|
+
await file.close();
|
|
18750
|
+
}
|
|
18751
|
+
await rename4(temporary, path);
|
|
18752
|
+
} finally {
|
|
18753
|
+
await unlink5(temporary).catch(() => void 0);
|
|
18754
|
+
}
|
|
18755
|
+
}
|
|
18756
|
+
|
|
18269
18757
|
// src/commands/repl.ts
|
|
18270
18758
|
var HELP = `Commands:
|
|
18271
18759
|
/help Show this help
|
|
@@ -18296,7 +18784,7 @@ var HELP = `Commands:
|
|
|
18296
18784
|
/whoami Show sign-in state
|
|
18297
18785
|
/tools List available tools
|
|
18298
18786
|
/logs Show the private diagnostic log path
|
|
18299
|
-
/ghost [on|off|status]
|
|
18787
|
+
/ghost [on|off|status] Quiet activity output; diagnostics stay enabled
|
|
18300
18788
|
/output [number|list] Inspect saved tool output (latest by default)
|
|
18301
18789
|
/memory List and inspect stored memories
|
|
18302
18790
|
/skill <name> [args] Run an installed skill
|
|
@@ -18467,6 +18955,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18467
18955
|
bindSignals: false,
|
|
18468
18956
|
details: { provider: provider.name, model: session.model, mode: session.mode, status: "Idle" }
|
|
18469
18957
|
}) : void 0;
|
|
18958
|
+
setActivitySink(layout?.enabled ? (snapshot) => layout.setActivity(snapshot) : void 0);
|
|
18470
18959
|
const refreshDetails = () => {
|
|
18471
18960
|
if (!layout?.enabled) return;
|
|
18472
18961
|
const steps = session.toSession().steps;
|
|
@@ -18504,7 +18993,8 @@ async function runRepl(options, initialTask = "") {
|
|
|
18504
18993
|
workspace,
|
|
18505
18994
|
useNativeTools ? "native tools" : "json protocol",
|
|
18506
18995
|
describeBudget(mode, toolSelection, registry),
|
|
18507
|
-
diagnosticPath2
|
|
18996
|
+
diagnosticPath2,
|
|
18997
|
+
provider.name.toLowerCase() === "ultrac" ? await readAccountPlan(await resolveUltracCredentials()) : void 0
|
|
18508
18998
|
);
|
|
18509
18999
|
if (mcpRuntime && mcpRuntime.statuses.length > 0) {
|
|
18510
19000
|
console.log(chalk11.bold("\nMCP servers"));
|
|
@@ -18517,15 +19007,14 @@ async function runRepl(options, initialTask = "") {
|
|
|
18517
19007
|
throw new Error(`Could not start the session: ${errorMessage2(error)}`, { cause: error });
|
|
18518
19008
|
}
|
|
18519
19009
|
const update = await updateCheck;
|
|
18520
|
-
if (update?.updateAvailable && update.latestVersion) {
|
|
18521
|
-
console.log(chalk11.yellow(`
|
|
18522
|
-
\u2191 Update available: ${update.currentVersion} \u2192 ${update.latestVersion}`));
|
|
18523
|
-
console.log(chalk11.gray(" Run `ultrac update` after this session."));
|
|
19010
|
+
if (!stdin5.isTTY && update?.updateAvailable && update.latestVersion) {
|
|
19011
|
+
console.log(chalk11.yellow(`Update available: ${update.currentVersion} \u2192 ${update.latestVersion}. Run ultrac update.`));
|
|
18524
19012
|
}
|
|
18525
19013
|
const rl = createInterface3({ input: stdin5, output: layout?.enabled ? layout.outputProxy : stdout5 });
|
|
18526
19014
|
layout?.attachReadline(rl);
|
|
19015
|
+
layout?.setInputFormatter(formatImageInput);
|
|
18527
19016
|
refreshDetails();
|
|
18528
|
-
const builtinEntries = BUILTIN_SLASH_COMMANDS
|
|
19017
|
+
const builtinEntries = builtinSlashEntries(BUILTIN_SLASH_COMMANDS, HELP);
|
|
18529
19018
|
const slashCompletion = layout?.enabled ? attachSlashCompletion(rl, async () => {
|
|
18530
19019
|
const [commands, skills] = await Promise.all([listSkillCommands(workspace), listSkills(workspace)]);
|
|
18531
19020
|
const declared = new Set(commands.commands.map((command) => command.skillName));
|
|
@@ -18605,29 +19094,35 @@ async function runRepl(options, initialTask = "") {
|
|
|
18605
19094
|
rl.close();
|
|
18606
19095
|
};
|
|
18607
19096
|
process.on("SIGTERM", terminate);
|
|
18608
|
-
let turnStartedAt;
|
|
18609
|
-
const footerTimer = setInterval(() => {
|
|
18610
|
-
const seconds = turnStartedAt === void 0 ? void 0 : Math.floor((Date.now() - turnStartedAt) / 1e3);
|
|
18611
|
-
layout?.updateDetails({ status: seconds === void 0 ? "Idle" : `Running ${Math.floor(seconds / 60).toString().padStart(2, "0")}:${(seconds % 60).toString().padStart(2, "0")}` });
|
|
18612
|
-
}, 1e3);
|
|
18613
|
-
footerTimer.unref();
|
|
18614
19097
|
const withInterrupt = async (run) => {
|
|
18615
19098
|
turnAbort = new AbortController();
|
|
18616
|
-
|
|
18617
|
-
layout?.updateDetails({ status: "Running 00:00" });
|
|
19099
|
+
layout?.updateDetails({ status: "Running" });
|
|
18618
19100
|
try {
|
|
18619
19101
|
return await run(turnAbort.signal);
|
|
18620
19102
|
} finally {
|
|
18621
19103
|
turnAbort = void 0;
|
|
18622
|
-
turnStartedAt = void 0;
|
|
18623
19104
|
refreshDetails();
|
|
18624
19105
|
layout?.updateDetails({ status: "Idle" });
|
|
18625
19106
|
}
|
|
18626
19107
|
};
|
|
18627
19108
|
try {
|
|
19109
|
+
if (update) {
|
|
19110
|
+
const decision = await withInterrupt((signal) => offerUpdatePrompt(update, {
|
|
19111
|
+
interactive: Boolean(stdin5.isTTY && stdout5.isTTY),
|
|
19112
|
+
signal,
|
|
19113
|
+
ask: (text, choices, askSignal) => getAskUserRuntime()(text, [...choices], askSignal)
|
|
19114
|
+
}));
|
|
19115
|
+
if (decision === "update_now") {
|
|
19116
|
+
layout?.close();
|
|
19117
|
+
rl.close();
|
|
19118
|
+
await installVerifiedVersion(packageInfo.name, update.latestVersion);
|
|
19119
|
+
console.log(`Updated to ${update.latestVersion}. Run ultrac again to use the new version.`);
|
|
19120
|
+
return;
|
|
19121
|
+
}
|
|
19122
|
+
}
|
|
18628
19123
|
if (initialTask) {
|
|
18629
19124
|
console.log(chalk11.white(`
|
|
18630
|
-
\u203A ${initialTask}`));
|
|
19125
|
+
\u203A ${formatImageInput(initialTask).text}`));
|
|
18631
19126
|
await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds));
|
|
18632
19127
|
}
|
|
18633
19128
|
while (running) {
|
|
@@ -18642,10 +19137,10 @@ async function runRepl(options, initialTask = "") {
|
|
|
18642
19137
|
}
|
|
18643
19138
|
const line = await reader.next();
|
|
18644
19139
|
if (line === null) break;
|
|
18645
|
-
if (layout?.enabled && line.trim()) console.log(`\u203A ${line.replace(/[\x00-\x1f\x7f]/g, " ")}`);
|
|
19140
|
+
if (layout?.enabled && line.trim()) console.log(`\u203A ${formatImageInput(line).text.replace(/[\x00-\x1f\x7f]/g, " ")}`);
|
|
18646
19141
|
const raw = line.trim();
|
|
18647
19142
|
if (!raw) continue;
|
|
18648
|
-
if (raw.startsWith("/")) {
|
|
19143
|
+
if (raw.startsWith("/") && !hasDroppedImage(raw)) {
|
|
18649
19144
|
const previousSession = session;
|
|
18650
19145
|
const slashCommand = raw.slice(1).trim().split(/\s+/, 1)[0]?.toLowerCase();
|
|
18651
19146
|
const handled = await withInterrupt(async (signal) => {
|
|
@@ -18658,7 +19153,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18658
19153
|
resetContextBudgetNotices();
|
|
18659
19154
|
console.log(chalk11.gray("Started a fresh conversation."));
|
|
18660
19155
|
}, async (prompt) => {
|
|
18661
|
-
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds);
|
|
19156
|
+
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds, false);
|
|
18662
19157
|
}, signal, async (id) => {
|
|
18663
19158
|
const lineage = await loadSessionLineage(workspace, id);
|
|
18664
19159
|
const restored = createSession({ model: session.model, mode: session.mode, permissions: session.currentPermissions });
|
|
@@ -18681,7 +19176,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18681
19176
|
if (handled === "unhandled") {
|
|
18682
19177
|
try {
|
|
18683
19178
|
await withInterrupt((signal) => runSkillCommand(session, raw, workspace, question, async (prompt) => {
|
|
18684
|
-
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds);
|
|
19179
|
+
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds, false);
|
|
18685
19180
|
}));
|
|
18686
19181
|
} finally {
|
|
18687
19182
|
await persistSession();
|
|
@@ -18696,11 +19191,11 @@ async function runRepl(options, initialTask = "") {
|
|
|
18696
19191
|
await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds));
|
|
18697
19192
|
}
|
|
18698
19193
|
} finally {
|
|
18699
|
-
clearInterval(footerTimer);
|
|
18700
19194
|
slashCompletion?.cleanup();
|
|
18701
19195
|
process.off("SIGTERM", terminate);
|
|
18702
19196
|
if (stdin5.isTTY) stdin5.off("keypress", onKeypress);
|
|
18703
19197
|
setAskUserRuntime(void 0);
|
|
19198
|
+
setActivitySink(void 0);
|
|
18704
19199
|
rl.close();
|
|
18705
19200
|
try {
|
|
18706
19201
|
await saveTranscript(session, writeCheckpoint);
|
|
@@ -18711,14 +19206,33 @@ async function runRepl(options, initialTask = "") {
|
|
|
18711
19206
|
}
|
|
18712
19207
|
console.log(chalk11.gray("Goodbye."));
|
|
18713
19208
|
}
|
|
18714
|
-
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing) {
|
|
19209
|
+
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop = true) {
|
|
19210
|
+
const activity = isGhostEnabled(session) ? void 0 : getActivitySink();
|
|
19211
|
+
const entries = [];
|
|
19212
|
+
let thinkingMs = 0;
|
|
19213
|
+
let thinkingStarted;
|
|
19214
|
+
let turnFailed = false;
|
|
19215
|
+
const refreshActivity = (finished = false) => activity?.({ entries: [...entries], thinkingMs, finished, failed: turnFailed });
|
|
19216
|
+
refreshActivity();
|
|
18715
19217
|
const spinner = new Spinner();
|
|
19218
|
+
const workSpinner = new Spinner("working");
|
|
19219
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
19220
|
+
let permissionPrompts = 0;
|
|
19221
|
+
const renderWorking = () => {
|
|
19222
|
+
workSpinner.stop();
|
|
19223
|
+
const active = [...activeTools.entries()].at(-1);
|
|
19224
|
+
if (!active || quiet || permissionPrompts || [...activeTools.keys()].some((call) => call.tool === "ask_user")) return;
|
|
19225
|
+
workSpinner.update(`working \xB7 ${toolActivityLabel(active[0])}${activeTools.size > 1 ? ` (+${activeTools.size - 1} active)` : ""}`);
|
|
19226
|
+
workSpinner.start(isComposing, active[1]);
|
|
19227
|
+
};
|
|
18716
19228
|
const quiet = isGhostEnabled(session);
|
|
18717
19229
|
const detachProgress = quiet ? () => {
|
|
18718
19230
|
} : attachProgressUi();
|
|
18719
19231
|
let deltaChars = 0;
|
|
18720
19232
|
try {
|
|
18721
|
-
await
|
|
19233
|
+
const prepared = allowImageDrop ? await prepareImageInput(input) : { text: input };
|
|
19234
|
+
await session.ask(prepared.text, {
|
|
19235
|
+
...prepared.image ? { image: prepared.image } : {},
|
|
18722
19236
|
...signal ? { signal } : {},
|
|
18723
19237
|
...persist ? {
|
|
18724
19238
|
onCheckpoint: async () => {
|
|
@@ -18731,13 +19245,22 @@ async function runTurn(session, input, question, signal, drainQueuedMessages, pe
|
|
|
18731
19245
|
},
|
|
18732
19246
|
onModelStart: () => {
|
|
18733
19247
|
deltaChars = 0;
|
|
18734
|
-
|
|
19248
|
+
thinkingStarted = performance.now();
|
|
19249
|
+
if (!quiet) {
|
|
19250
|
+
spinner.update("thinking");
|
|
19251
|
+
spinner.start(isComposing);
|
|
19252
|
+
}
|
|
18735
19253
|
},
|
|
18736
19254
|
onModelDelta: (text) => {
|
|
18737
19255
|
deltaChars += text.length;
|
|
18738
19256
|
if (!quiet) spinner.update(`thinking (${deltaChars} chars)`);
|
|
18739
19257
|
},
|
|
18740
|
-
onModelSettled: () =>
|
|
19258
|
+
onModelSettled: () => {
|
|
19259
|
+
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19260
|
+
thinkingStarted = void 0;
|
|
19261
|
+
spinner.stop(activity ? void 0 : signal?.aborted ? "Thinking stopped after" : "Thought for");
|
|
19262
|
+
refreshActivity();
|
|
19263
|
+
},
|
|
18741
19264
|
onAssistantMessage: (message) => {
|
|
18742
19265
|
if (message.trim()) {
|
|
18743
19266
|
console.log(`
|
|
@@ -18745,9 +19268,24 @@ ${message.trim()}`);
|
|
|
18745
19268
|
}
|
|
18746
19269
|
},
|
|
18747
19270
|
onToolStart: (call) => {
|
|
18748
|
-
|
|
19271
|
+
activeTools.set(call, performance.now());
|
|
19272
|
+
renderWorking();
|
|
19273
|
+
if (!stdout5.isTTY && !quiet) console.log(chalk11.dim(` ${toolActivityLabel(call)}`));
|
|
19274
|
+
},
|
|
19275
|
+
onToolResult: (call, result) => {
|
|
19276
|
+
workSpinner.stop();
|
|
19277
|
+
const started = activeTools.get(call);
|
|
19278
|
+
activeTools.delete(call);
|
|
19279
|
+
const duration = started === void 0 ? 0 : performance.now() - started;
|
|
19280
|
+
entries.push(activityEntry(call, result, duration));
|
|
19281
|
+
refreshActivity();
|
|
19282
|
+
if (!activity && !quiet) {
|
|
19283
|
+
const dot = result.ok ? chalk11.green("\u25CF") : chalk11.red("\u25CF");
|
|
19284
|
+
console.log(` ${dot} ${toolActivityLabel(call)}${duration >= 1e3 ? ` \xB7 Worked for ${formatSeconds(duration)}` : ""}`);
|
|
19285
|
+
}
|
|
19286
|
+
printToolResult2(result, Boolean(activity));
|
|
19287
|
+
renderWorking();
|
|
18749
19288
|
},
|
|
18750
|
-
onToolResult: (_call, result) => printToolResult2(result),
|
|
18751
19289
|
...drainQueuedMessages ? {
|
|
18752
19290
|
drainQueuedMessages,
|
|
18753
19291
|
onQueuedMessage: (message) => {
|
|
@@ -18771,42 +19309,55 @@ ${message.trim()}`);
|
|
|
18771
19309
|
console.log(chalk11.gray(` ${hint}`));
|
|
18772
19310
|
},
|
|
18773
19311
|
requestPermission: async (call, permission) => {
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
|
|
18786
|
-
|
|
19312
|
+
permissionPrompts += 1;
|
|
19313
|
+
workSpinner.stop();
|
|
19314
|
+
try {
|
|
19315
|
+
const prompt = permission === "shell" && call.tool === "run_shell" ? shellApprovalPrompt(call, session.toSession().workspace) : permission === "shell" ? ` ${call.tool} wants shell access. [o]nce (this call only) / [a]lways (all shell tools for this session) / [n]o (this call) / n[e]ver (this session) \u203A ` : ` ${call.tool} wants ${permission} access. [o]nce / [a]lways / [n]o / n[e]ver \u203A `;
|
|
19316
|
+
const rawAnswer = await question(chalk11.yellow(prompt), signal);
|
|
19317
|
+
if (signal?.aborted || rawAnswer === null) return "deny";
|
|
19318
|
+
const answer = rawAnswer.trim().toLowerCase();
|
|
19319
|
+
if (answer === "a" || answer === "always") return "always";
|
|
19320
|
+
if (answer === "o" || answer === "once" || answer === "y" || answer === "yes") return "once";
|
|
19321
|
+
if (answer === "e" || answer === "never") {
|
|
19322
|
+
console.log(
|
|
19323
|
+
chalk11.gray(
|
|
19324
|
+
` ${permission} refused for the rest of the session \u2014 nothing will ask again. /allow ${permission} undoes it.`
|
|
19325
|
+
)
|
|
19326
|
+
);
|
|
19327
|
+
return "never";
|
|
19328
|
+
}
|
|
19329
|
+
return "deny";
|
|
19330
|
+
} finally {
|
|
19331
|
+
permissionPrompts -= 1;
|
|
19332
|
+
renderWorking();
|
|
18787
19333
|
}
|
|
18788
|
-
return "deny";
|
|
18789
19334
|
}
|
|
18790
19335
|
});
|
|
18791
19336
|
return true;
|
|
18792
19337
|
} catch (error) {
|
|
19338
|
+
turnFailed = true;
|
|
18793
19339
|
spinner.stop();
|
|
18794
19340
|
printTurnError(error);
|
|
18795
19341
|
return false;
|
|
18796
19342
|
} finally {
|
|
19343
|
+
spinner.stop();
|
|
19344
|
+
workSpinner.stop();
|
|
19345
|
+
activeTools.clear();
|
|
19346
|
+
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19347
|
+
refreshActivity(true);
|
|
18797
19348
|
detachProgress();
|
|
18798
19349
|
}
|
|
18799
19350
|
}
|
|
18800
|
-
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages, isComposing, maxRounds = 1) {
|
|
19351
|
+
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages, isComposing, maxRounds = 1, allowImageDrop = true) {
|
|
18801
19352
|
try {
|
|
18802
|
-
if (maxRounds === 1) return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing);
|
|
19353
|
+
if (maxRounds === 1) return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop);
|
|
18803
19354
|
let successful = true;
|
|
18804
19355
|
const result = await runAutonomousLoop({
|
|
18805
19356
|
maxRounds,
|
|
18806
19357
|
signal,
|
|
18807
19358
|
classifyFailure: () => ({ kind: "unknown", retryable: false, failoverable: false, reason: "Interactive provider retries already exhausted." }),
|
|
18808
19359
|
runRound: async (round, _previous, roundSignal) => {
|
|
18809
|
-
successful = await runTurn(session, round === 1 ? input : AUTONOMOUS_CONTINUE_TASK, question, roundSignal, drainQueuedMessages, persist, isComposing);
|
|
19360
|
+
successful = await runTurn(session, round === 1 ? input : AUTONOMOUS_CONTINUE_TASK, question, roundSignal, drainQueuedMessages, persist, isComposing, allowImageDrop);
|
|
18810
19361
|
await persist(session);
|
|
18811
19362
|
const snapshot = session.toSession();
|
|
18812
19363
|
return successful ? snapshot : { ...snapshot, stopReason: roundSignal.aborted ? "aborted" : "error" };
|
|
@@ -19000,7 +19551,7 @@ ${extra}`);
|
|
|
19000
19551
|
else {
|
|
19001
19552
|
for (const field of ["inputTokens", "outputTokens", "totalTokens"]) {
|
|
19002
19553
|
const counts = reported.flatMap((step) => typeof step.usage?.[field] === "number" ? [step.usage[field]] : []);
|
|
19003
|
-
console.log(`${field}: ${counts.length ? counts.reduce((sum,
|
|
19554
|
+
console.log(`${field}: ${counts.length ? counts.reduce((sum, count3) => sum + count3, 0) : "not reported"}`);
|
|
19004
19555
|
}
|
|
19005
19556
|
console.log(`Counts cover ${reported.length}/${steps.length} agent steps in this session. Compaction calls and ancestor sessions are excluded. Card charges are not inferred from tokens.`);
|
|
19006
19557
|
}
|
|
@@ -19127,6 +19678,8 @@ ${extra}`);
|
|
|
19127
19678
|
}
|
|
19128
19679
|
const entry = results[selected - 1];
|
|
19129
19680
|
console.log(`${selected}. ${toolActivityLabel(entry.call)} (${entry.result.ok ? "ok" : "failed"})`);
|
|
19681
|
+
const detail = activityEntry(entry.call, entry.result, 0);
|
|
19682
|
+
if (detail.request) console.log(detail.request);
|
|
19130
19683
|
console.log(entry.result.content);
|
|
19131
19684
|
for (const warning of getMcpArchiveWarnings(entry.result)) console.log(chalk11.yellow(`Warning: ${warning}`));
|
|
19132
19685
|
return "ok";
|
|
@@ -19205,7 +19758,7 @@ async function runSkillCommand(session, raw, workspace, question, runPrompt) {
|
|
|
19205
19758
|
chalk11.gray(` /${resolved.command.command} \u2192 skill ${resolved.command.skillName} (${resolved.command.scope})`)
|
|
19206
19759
|
);
|
|
19207
19760
|
if (runPrompt) await runPrompt(resolved.prompt);
|
|
19208
|
-
else await runTurn(session, resolved.prompt, question);
|
|
19761
|
+
else await runTurn(session, resolved.prompt, question, void 0, void 0, void 0, void 0, false);
|
|
19209
19762
|
return;
|
|
19210
19763
|
}
|
|
19211
19764
|
if (resolved.status === "conflict") {
|
|
@@ -19270,7 +19823,7 @@ var LineReader = class {
|
|
|
19270
19823
|
const messages = [];
|
|
19271
19824
|
while (this.queue.length) {
|
|
19272
19825
|
const next = this.queue[0].trim();
|
|
19273
|
-
if (next.startsWith("/")) break;
|
|
19826
|
+
if (next.startsWith("/") || hasDroppedImage(next)) break;
|
|
19274
19827
|
this.queue.shift();
|
|
19275
19828
|
if (next) messages.push(next);
|
|
19276
19829
|
}
|
|
@@ -19283,8 +19836,8 @@ var LineReader = class {
|
|
|
19283
19836
|
if (buffered !== void 0) return Promise.resolve(buffered);
|
|
19284
19837
|
}
|
|
19285
19838
|
if (this.closed) return Promise.resolve(null);
|
|
19286
|
-
return new Promise((
|
|
19287
|
-
const waiter = { resolve:
|
|
19839
|
+
return new Promise((resolve17) => {
|
|
19840
|
+
const waiter = { resolve: resolve17, ...signal ? { signal } : {} };
|
|
19288
19841
|
if (signal) {
|
|
19289
19842
|
waiter.onAbort = () => {
|
|
19290
19843
|
const index = this.waiters.indexOf(waiter);
|
|
@@ -19317,11 +19870,12 @@ function handleReplExit(activeTurn) {
|
|
|
19317
19870
|
activeTurn?.abort();
|
|
19318
19871
|
return "exit-session";
|
|
19319
19872
|
}
|
|
19320
|
-
function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2) {
|
|
19873
|
+
function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2, planName) {
|
|
19321
19874
|
console.log(renderWelcome(
|
|
19322
19875
|
{
|
|
19323
19876
|
version,
|
|
19324
19877
|
providerName,
|
|
19878
|
+
planName,
|
|
19325
19879
|
model,
|
|
19326
19880
|
mode,
|
|
19327
19881
|
workspace,
|
|
@@ -19346,9 +19900,9 @@ function describeAdvertisedTools(mode, selection, registry = createDefaultToolRe
|
|
|
19346
19900
|
names: advertised.map((tool) => tool.name)
|
|
19347
19901
|
};
|
|
19348
19902
|
}
|
|
19349
|
-
function printToolResult2(result) {
|
|
19903
|
+
function printToolResult2(result, collapsed = false) {
|
|
19350
19904
|
const failure = toolFailurePreview(result);
|
|
19351
|
-
if (failure) console.log(chalk11.red(` Failed: ${failure}`));
|
|
19905
|
+
if (failure && !collapsed) console.log(chalk11.red(` Failed: ${failure}`));
|
|
19352
19906
|
for (const warning of getMcpArchiveWarnings(result)) {
|
|
19353
19907
|
console.log(chalk11.yellow(indent3(`Warning: ${warning}`)));
|
|
19354
19908
|
}
|
|
@@ -19624,7 +20178,7 @@ ${summary}` : ""}`).catch((error) => console.error(chalk12.yellow(`Could not sen
|
|
|
19624
20178
|
}
|
|
19625
20179
|
|
|
19626
20180
|
// src/commands/sessions.ts
|
|
19627
|
-
import { resolve as
|
|
20181
|
+
import { resolve as resolve14 } from "path";
|
|
19628
20182
|
function registerSessionsCommand(program2) {
|
|
19629
20183
|
const sessions = program2.command("sessions").description("Inspect saved Ultrac session transcripts");
|
|
19630
20184
|
sessions.command("list").description("List saved sessions").option("-w, --workspace <path>", "Workspace path", ".").option("--json", "Print JSON output", false).option("--max-results <number>", "Maximum sessions to show", "20").action(async (options) => {
|
|
@@ -19641,7 +20195,7 @@ function registerSessionsCommand(program2) {
|
|
|
19641
20195
|
);
|
|
19642
20196
|
});
|
|
19643
20197
|
sessions.command("export").description("Export a saved session as an auditable episode package").argument("<id>", "Session id").option("-w, --workspace <path>", "Workspace path", ".").option("--format <format>", "Output format: markdown or json", "markdown").option("--max-chars <number>", "Maximum characters per model/tool text block in markdown", "4000").option("--outcome <outcome>", "Outcome status: pass, fail, blocked, or unknown", "unknown").option("--notes <text>", "Outcome notes to include in the episode package").option("--out <path>", "Write export to a path inside the workspace instead of stdout").action(async (id, options) => {
|
|
19644
|
-
const workspace =
|
|
20198
|
+
const workspace = resolve14(options.workspace);
|
|
19645
20199
|
const format3 = parseExportFormat(options.format);
|
|
19646
20200
|
const maxChars = parsePositiveInt2(options.maxChars);
|
|
19647
20201
|
const outcome = parseOutcomeStatus(options.outcome);
|
|
@@ -19658,7 +20212,7 @@ function registerSessionsCommand(program2) {
|
|
|
19658
20212
|
});
|
|
19659
20213
|
}
|
|
19660
20214
|
async function runSessionTool(tool, input, options) {
|
|
19661
|
-
const workspace =
|
|
20215
|
+
const workspace = resolve14(options.workspace);
|
|
19662
20216
|
const result = await createDefaultToolRegistry().execute(
|
|
19663
20217
|
{ tool, input },
|
|
19664
20218
|
{ workspace, permissions: { write: false, shell: false, network: false } }
|
|
@@ -19834,11 +20388,11 @@ function collectToolArtifacts(session, toolName) {
|
|
|
19834
20388
|
}
|
|
19835
20389
|
|
|
19836
20390
|
// src/commands/skills.ts
|
|
19837
|
-
import { resolve as
|
|
20391
|
+
import { resolve as resolve15 } from "path";
|
|
19838
20392
|
function registerSkillsCommand(program2) {
|
|
19839
20393
|
const skills = program2.command("skills").description("Manage Ultrac skills (SKILL.md playbooks)");
|
|
19840
20394
|
skills.command("list").description("List installed skills").option("-w, --workspace <path>", "Workspace path", ".").action(async (options) => {
|
|
19841
|
-
const installed = await listSkills(
|
|
20395
|
+
const installed = await listSkills(resolve15(options.workspace));
|
|
19842
20396
|
if (!installed.length) {
|
|
19843
20397
|
console.log("No skills installed. Create one with: ultrac skills create <name> --description <text>");
|
|
19844
20398
|
return;
|
|
@@ -19849,7 +20403,7 @@ function registerSkillsCommand(program2) {
|
|
|
19849
20403
|
}
|
|
19850
20404
|
});
|
|
19851
20405
|
skills.command("show").description("Show a skill's full instructions").argument("<name>", "Skill name").option("-w, --workspace <path>", "Workspace path", ".").action(async (name, options) => {
|
|
19852
|
-
const skill = await readSkill(
|
|
20406
|
+
const skill = await readSkill(resolve15(options.workspace), name);
|
|
19853
20407
|
if (!skill) throw new Error(`No skill named "${name}".`);
|
|
19854
20408
|
console.log(`# ${skill.name} (${skill.scope})`);
|
|
19855
20409
|
console.log(`Path: ${skill.path}`);
|
|
@@ -19864,7 +20418,7 @@ function registerSkillsCommand(program2) {
|
|
|
19864
20418
|
if (options.argumentHint && !command) throw new Error("--argument-hint requires --command.");
|
|
19865
20419
|
const argumentHint = options.argumentHint?.trim();
|
|
19866
20420
|
if (argumentHint && (argumentHint.length > 256 || /[\r\n]/.test(argumentHint))) throw new Error("Argument hints must be one line, at most 256 characters.");
|
|
19867
|
-
const skill = await createSkill(
|
|
20421
|
+
const skill = await createSkill(resolve15(options.workspace), {
|
|
19868
20422
|
name,
|
|
19869
20423
|
description: options.description,
|
|
19870
20424
|
whenToUse: options.when,
|
|
@@ -19879,7 +20433,7 @@ function registerSkillsCommand(program2) {
|
|
|
19879
20433
|
}
|
|
19880
20434
|
);
|
|
19881
20435
|
skills.command("path").description("Print the skills directory path for a scope").option("-w, --workspace <path>", "Workspace path", ".").option("--scope <scope>", "global or project", "project").action((options) => {
|
|
19882
|
-
console.log(skillsDir(parseScope2(options.scope),
|
|
20436
|
+
console.log(skillsDir(parseScope2(options.scope), resolve15(options.workspace)));
|
|
19883
20437
|
});
|
|
19884
20438
|
}
|
|
19885
20439
|
function parseScope2(scope) {
|
|
@@ -20181,13 +20735,13 @@ var FollowAbortedError = class extends Error {
|
|
|
20181
20735
|
}
|
|
20182
20736
|
};
|
|
20183
20737
|
function defaultSleep2(ms, signal) {
|
|
20184
|
-
return new Promise((
|
|
20185
|
-
if (signal?.aborted) return
|
|
20738
|
+
return new Promise((resolve17) => {
|
|
20739
|
+
if (signal?.aborted) return resolve17();
|
|
20186
20740
|
const timer = setTimeout(done, ms);
|
|
20187
20741
|
function done() {
|
|
20188
20742
|
signal?.removeEventListener("abort", done);
|
|
20189
20743
|
clearTimeout(timer);
|
|
20190
|
-
|
|
20744
|
+
resolve17();
|
|
20191
20745
|
}
|
|
20192
20746
|
signal?.addEventListener("abort", done, { once: true });
|
|
20193
20747
|
});
|
|
@@ -20620,8 +21174,8 @@ Leaving run ${runId} as it is.
|
|
|
20620
21174
|
case "succeeded":
|
|
20621
21175
|
finishHuman(result.text);
|
|
20622
21176
|
if (result.toolCalls.length && !result.text) {
|
|
20623
|
-
const
|
|
20624
|
-
r.err(chalk13.gray(`Done with ${
|
|
21177
|
+
const count3 = result.toolCalls.length;
|
|
21178
|
+
r.err(chalk13.gray(`Done with ${count3} tool call${count3 === 1 ? "" : "s"} and no text.
|
|
20625
21179
|
`));
|
|
20626
21180
|
}
|
|
20627
21181
|
return result.outputSource === "incomplete" ? EXIT_FAILED : EXIT_DONE;
|
|
@@ -20683,11 +21237,11 @@ async function interrupted(r, runId, options, printed) {
|
|
|
20683
21237
|
}
|
|
20684
21238
|
async function resolveDeps(options, deps) {
|
|
20685
21239
|
if (options.baseUrl) setBaseUrlFlag(options.baseUrl);
|
|
20686
|
-
const
|
|
21240
|
+
const resolve17 = deps.credentials ?? (async () => {
|
|
20687
21241
|
const credentials2 = await resolveUltracCredentials();
|
|
20688
21242
|
return credentials2 ? { apiKey: credentials2.apiKey, baseUrl: credentials2.baseUrl } : null;
|
|
20689
21243
|
});
|
|
20690
|
-
const credentials = await
|
|
21244
|
+
const credentials = await resolve17();
|
|
20691
21245
|
if (!credentials) {
|
|
20692
21246
|
throw new Error("Not signed in to Ultrac. Run `ultrac login` (or set ULTRAC_API_KEY).");
|
|
20693
21247
|
}
|
|
@@ -20786,7 +21340,7 @@ ${run.output}${run.output.endsWith("\n") ? "" : "\n"}`;
|
|
|
20786
21340
|
}
|
|
20787
21341
|
|
|
20788
21342
|
// src/commands/tools.ts
|
|
20789
|
-
import { resolve as
|
|
21343
|
+
import { resolve as resolve16 } from "path";
|
|
20790
21344
|
import chalk14 from "chalk";
|
|
20791
21345
|
function registerToolsCommand(program2) {
|
|
20792
21346
|
const toolsCommand = program2.command("tools");
|
|
@@ -20821,7 +21375,7 @@ ${tools.length} of ${all.length} tools advertised in ${options.mode} mode, ${ser
|
|
|
20821
21375
|
const result = await createDefaultToolRegistry().execute(
|
|
20822
21376
|
{ tool, input },
|
|
20823
21377
|
{
|
|
20824
|
-
workspace:
|
|
21378
|
+
workspace: resolve16(options.workspace),
|
|
20825
21379
|
permissions: {
|
|
20826
21380
|
write: options.allowWrite,
|
|
20827
21381
|
shell: options.allowShell,
|