@spinalfluid/ultrac 0.1.11 → 0.1.18
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 +30 -10
- package/dist/cli.js +1047 -284
- 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;
|
|
@@ -1170,7 +1174,7 @@ var COMPACTION_MARKER = "[compacted-context]";
|
|
|
1170
1174
|
var SUMMARY_PREAMBLE = "The earlier part of this session was removed to fit the context window and replaced by the record below. It is a record of what happened, not a new instruction. Continue the work it describes.";
|
|
1171
1175
|
var DEFAULT_MAX_SUMMARY_CHARS = 8e3;
|
|
1172
1176
|
var MIN_SUMMARY_CHARS = 400;
|
|
1173
|
-
var DEFAULT_SUMMARY_TIMEOUT_MS =
|
|
1177
|
+
var DEFAULT_SUMMARY_TIMEOUT_MS = 12e4;
|
|
1174
1178
|
var DEFAULT_MAX_TRANSCRIPT_CHARS = 6e4;
|
|
1175
1179
|
var DEFAULT_MAX_MESSAGE_CHARS = 4e3;
|
|
1176
1180
|
function isCompactionSummary(message) {
|
|
@@ -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 {
|
|
@@ -2571,6 +2584,8 @@ function classifyProviderError(error) {
|
|
|
2571
2584
|
return transient("timeout", "Provider request timed out.");
|
|
2572
2585
|
}
|
|
2573
2586
|
const status = extractStatus(error);
|
|
2587
|
+
const busy = [...errorChain(error)].some((current) => readString(current, "code") === "upstream_busy");
|
|
2588
|
+
if (busy) return { ...transient("server", "Model capacity or admission is temporarily unavailable (HTTP 503).", status ?? 503), code: "upstream_busy", retryAfterMs: retryDelay(error) ?? 5e3 };
|
|
2574
2589
|
if (hasNotConfiguredCode(error)) {
|
|
2575
2590
|
return terminal(
|
|
2576
2591
|
"not_configured",
|
|
@@ -2585,8 +2600,8 @@ function classifyProviderError(error) {
|
|
|
2585
2600
|
}
|
|
2586
2601
|
if (typeof status === "number") {
|
|
2587
2602
|
if (status === 408) return transient("timeout", "HTTP 408 request timeout.", status);
|
|
2588
|
-
if (status === 429) return transient("rate_limit", "HTTP 429 rate limited.", status);
|
|
2589
|
-
if (status >= 500) return transient("server", `HTTP ${status} server error.`, status);
|
|
2603
|
+
if (status === 429) return { ...transient("rate_limit", "HTTP 429 rate limited.", status), retryAfterMs: retryDelay(error) };
|
|
2604
|
+
if (status >= 500) return { ...transient("server", `HTTP ${status} server error.`, status), retryAfterMs: retryDelay(error) };
|
|
2590
2605
|
if (status === 401 || status === 403) {
|
|
2591
2606
|
return terminal("auth", `HTTP ${status} authentication failure.`, status);
|
|
2592
2607
|
}
|
|
@@ -2627,9 +2642,24 @@ function safeClassification(classification) {
|
|
|
2627
2642
|
...status === void 0 ? {} : { status },
|
|
2628
2643
|
retryable: classification.retryable,
|
|
2629
2644
|
failoverable: classification.failoverable,
|
|
2630
|
-
reason
|
|
2645
|
+
reason: classification.code === "upstream_busy" ? "Model capacity or admission is temporarily unavailable (HTTP 503)." : reason,
|
|
2646
|
+
...classification.code ? { code: classification.code } : {},
|
|
2647
|
+
...classification.retryAfterMs === void 0 ? {} : { retryAfterMs: classification.retryAfterMs }
|
|
2631
2648
|
};
|
|
2632
2649
|
}
|
|
2650
|
+
function retryDelay(error) {
|
|
2651
|
+
for (const current of errorChain(error)) {
|
|
2652
|
+
if (!isRecord2(current)) continue;
|
|
2653
|
+
const headers = current.headers;
|
|
2654
|
+
if (typeof headers?.get !== "function") continue;
|
|
2655
|
+
const raw = headers.get("retry-after");
|
|
2656
|
+
if (!raw) continue;
|
|
2657
|
+
const seconds = Number(raw);
|
|
2658
|
+
const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(raw) - Date.now();
|
|
2659
|
+
if (Number.isFinite(ms) && ms >= 0) return Math.min(ms, 3e5);
|
|
2660
|
+
}
|
|
2661
|
+
return void 0;
|
|
2662
|
+
}
|
|
2633
2663
|
function classificationReason(kind, status) {
|
|
2634
2664
|
switch (kind) {
|
|
2635
2665
|
case "aborted":
|
|
@@ -2723,17 +2753,17 @@ function computeBackoffDelay(attempt, options) {
|
|
|
2723
2753
|
return Math.round(capped * (0.5 + random() * 0.5));
|
|
2724
2754
|
}
|
|
2725
2755
|
function defaultSleep(ms, cancel) {
|
|
2726
|
-
return new Promise((
|
|
2756
|
+
return new Promise((resolve17) => {
|
|
2727
2757
|
if (cancel?.aborted) {
|
|
2728
|
-
|
|
2758
|
+
resolve17();
|
|
2729
2759
|
return;
|
|
2730
2760
|
}
|
|
2731
|
-
const timer = setTimeout(
|
|
2761
|
+
const timer = setTimeout(resolve17, ms);
|
|
2732
2762
|
cancel?.addEventListener(
|
|
2733
2763
|
"abort",
|
|
2734
2764
|
() => {
|
|
2735
2765
|
clearTimeout(timer);
|
|
2736
|
-
|
|
2766
|
+
resolve17();
|
|
2737
2767
|
},
|
|
2738
2768
|
{ once: true }
|
|
2739
2769
|
);
|
|
@@ -2808,10 +2838,19 @@ var FallbackProvider = class {
|
|
|
2808
2838
|
);
|
|
2809
2839
|
}
|
|
2810
2840
|
async chatWithTools(request) {
|
|
2811
|
-
return this.run("chatWithTools", request.signal, request.model, (entry, model, signal) => {
|
|
2841
|
+
return this.run("chatWithTools", request.signal, request.model, (entry, model, signal, touch) => {
|
|
2812
2842
|
if (entry.provider.supportsToolCalling && entry.provider.chatWithTools) {
|
|
2813
|
-
return entry.provider.chatWithTools({
|
|
2843
|
+
return entry.provider.chatWithTools({
|
|
2844
|
+
...request,
|
|
2845
|
+
model,
|
|
2846
|
+
signal,
|
|
2847
|
+
onProgress: () => {
|
|
2848
|
+
touch();
|
|
2849
|
+
request.onProgress?.();
|
|
2850
|
+
}
|
|
2851
|
+
});
|
|
2814
2852
|
}
|
|
2853
|
+
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
2854
|
return this.chatThroughToolProtocol(entry, model, request, signal);
|
|
2816
2855
|
});
|
|
2817
2856
|
}
|
|
@@ -2896,7 +2935,8 @@ var FallbackProvider = class {
|
|
|
2896
2935
|
const result = await this.withDeadline(
|
|
2897
2936
|
entry.name,
|
|
2898
2937
|
signal,
|
|
2899
|
-
(attemptSignal) => invoke(entry, model, attemptSignal)
|
|
2938
|
+
(attemptSignal, touch) => invoke(entry, model, attemptSignal, touch),
|
|
2939
|
+
operation === "chatWithTools"
|
|
2900
2940
|
);
|
|
2901
2941
|
this.active = target;
|
|
2902
2942
|
trace.end({ outcome: "ok" });
|
|
@@ -3020,7 +3060,7 @@ var FallbackProvider = class {
|
|
|
3020
3060
|
}
|
|
3021
3061
|
}
|
|
3022
3062
|
async waitBeforeRetry(operation, target, attempt, classification, signal) {
|
|
3023
|
-
const delayMs = computeBackoffDelay(attempt, this.backoff);
|
|
3063
|
+
const delayMs = Math.max(computeBackoffDelay(attempt, this.backoff), classification.retryAfterMs ?? 0);
|
|
3024
3064
|
const safe = safeClassification(classification);
|
|
3025
3065
|
this.onRetry?.({
|
|
3026
3066
|
operation,
|
|
@@ -3116,10 +3156,10 @@ var FallbackProvider = class {
|
|
|
3116
3156
|
error.name = "AbortError";
|
|
3117
3157
|
throw error;
|
|
3118
3158
|
}
|
|
3119
|
-
async withDeadline(provider, signal, run) {
|
|
3120
|
-
const deadline = this.createDeadline(provider, signal);
|
|
3159
|
+
async withDeadline(provider, signal, run, inactivity = false) {
|
|
3160
|
+
const deadline = this.createDeadline(provider, signal, inactivity);
|
|
3121
3161
|
try {
|
|
3122
|
-
return await deadline.race(run(deadline.signal));
|
|
3162
|
+
return await deadline.race(run(deadline.signal, deadline.touch));
|
|
3123
3163
|
} finally {
|
|
3124
3164
|
deadline.clear();
|
|
3125
3165
|
}
|
|
@@ -3129,31 +3169,55 @@ var FallbackProvider = class {
|
|
|
3129
3169
|
* only ever rejects while the deadline is live, so clearing it can never leave
|
|
3130
3170
|
* an unhandled rejection behind.
|
|
3131
3171
|
*/
|
|
3132
|
-
createDeadline(provider, signal) {
|
|
3172
|
+
createDeadline(provider, signal, inactivity = false) {
|
|
3133
3173
|
if (this.requestTimeoutMs <= 0) {
|
|
3134
|
-
return { signal, race: (promise) => promise, clear: () => void 0 };
|
|
3174
|
+
return { signal, race: (promise) => promise, clear: () => void 0, touch: () => void 0 };
|
|
3135
3175
|
}
|
|
3136
3176
|
const controller = new AbortController();
|
|
3137
|
-
|
|
3177
|
+
let cleared = false;
|
|
3178
|
+
let idleTimer;
|
|
3179
|
+
const hardTimer = new AbortController();
|
|
3180
|
+
let rejectExpired;
|
|
3181
|
+
const expired = new Promise((_, reject) => {
|
|
3182
|
+
rejectExpired = reject;
|
|
3183
|
+
});
|
|
3184
|
+
const forwardAbort = () => {
|
|
3185
|
+
const reason = signal?.reason ?? Object.assign(new Error("Request was aborted."), { name: "AbortError" });
|
|
3186
|
+
rejectExpired(reason);
|
|
3187
|
+
controller.abort(reason);
|
|
3188
|
+
};
|
|
3189
|
+
const expire = (timeoutMs) => {
|
|
3190
|
+
const error = new ProviderTimeoutError(timeoutMs, provider);
|
|
3191
|
+
rejectExpired(error);
|
|
3192
|
+
controller.abort(error);
|
|
3193
|
+
};
|
|
3194
|
+
const touch = () => {
|
|
3195
|
+
if (cleared || controller.signal.aborted) return;
|
|
3196
|
+
idleTimer?.abort();
|
|
3197
|
+
const timer = new AbortController();
|
|
3198
|
+
idleTimer = timer;
|
|
3199
|
+
void this.sleep(this.requestTimeoutMs, timer.signal).then(() => {
|
|
3200
|
+
if (!cleared && !timer.signal.aborted) expire(this.requestTimeoutMs);
|
|
3201
|
+
});
|
|
3202
|
+
};
|
|
3203
|
+
touch();
|
|
3204
|
+
if (inactivity) {
|
|
3205
|
+
void this.sleep(3e5, hardTimer.signal).then(() => {
|
|
3206
|
+
if (!cleared && !hardTimer.signal.aborted) expire(3e5);
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3138
3209
|
if (signal) {
|
|
3139
3210
|
if (signal.aborted) forwardAbort();
|
|
3140
3211
|
else signal.addEventListener("abort", forwardAbort, { once: true });
|
|
3141
3212
|
}
|
|
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
3213
|
return {
|
|
3152
3214
|
signal: controller.signal,
|
|
3153
3215
|
race: (promise) => Promise.race([promise, expired]),
|
|
3216
|
+
touch: inactivity ? touch : () => void 0,
|
|
3154
3217
|
clear: () => {
|
|
3155
3218
|
cleared = true;
|
|
3156
|
-
|
|
3219
|
+
idleTimer?.abort();
|
|
3220
|
+
hardTimer.abort();
|
|
3157
3221
|
signal?.removeEventListener("abort", forwardAbort);
|
|
3158
3222
|
}
|
|
3159
3223
|
};
|
|
@@ -4066,7 +4130,7 @@ function runProcess(command, args, options) {
|
|
|
4066
4130
|
});
|
|
4067
4131
|
}
|
|
4068
4132
|
const { env: env2, redactedEnvKeys } = createSanitizedProcessEnv();
|
|
4069
|
-
return new Promise((
|
|
4133
|
+
return new Promise((resolve17, reject) => {
|
|
4070
4134
|
const child = spawn(command, args, {
|
|
4071
4135
|
cwd: options.cwd,
|
|
4072
4136
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -4151,7 +4215,7 @@ function runProcess(command, args, options) {
|
|
|
4151
4215
|
child.on("error", (error) => {
|
|
4152
4216
|
finish(() => {
|
|
4153
4217
|
if (termination === "aborted") {
|
|
4154
|
-
|
|
4218
|
+
resolve17({
|
|
4155
4219
|
stdout: stdout6.text(),
|
|
4156
4220
|
stderr: stderr2.text(),
|
|
4157
4221
|
started,
|
|
@@ -4173,7 +4237,7 @@ function runProcess(command, args, options) {
|
|
|
4173
4237
|
const stdoutTruncation = stdout6.truncation();
|
|
4174
4238
|
const stderrTruncation = stderr2.truncation();
|
|
4175
4239
|
finish(
|
|
4176
|
-
() =>
|
|
4240
|
+
() => resolve17({
|
|
4177
4241
|
stdout: stdout6.text(),
|
|
4178
4242
|
stderr: stderr2.text(),
|
|
4179
4243
|
started,
|
|
@@ -5185,7 +5249,7 @@ function topCounts(values) {
|
|
|
5185
5249
|
for (const value of values) {
|
|
5186
5250
|
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
5187
5251
|
}
|
|
5188
|
-
return [...counts.entries()].map(([name,
|
|
5252
|
+
return [...counts.entries()].map(([name, count3]) => ({ name, count: count3 })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
5189
5253
|
}
|
|
5190
5254
|
async function collectGitContext(workspace, signal) {
|
|
5191
5255
|
const statusResult = await runGit(["status", "--short", "--branch"], { cwd: workspace, signal });
|
|
@@ -5377,7 +5441,7 @@ var diagnosticsTool = {
|
|
|
5377
5441
|
const byFile = summarizeByFile(matched);
|
|
5378
5442
|
const records = matched.slice(0, input.maxRecords);
|
|
5379
5443
|
const checkerExit = runs[0].exitCode;
|
|
5380
|
-
const
|
|
5444
|
+
const clean2 = matched.length === 0;
|
|
5381
5445
|
const unparsed = matched.length === 0 && runs.some((run) => run.exitCode !== 0);
|
|
5382
5446
|
const rawTail = unparsed ? tail(runs.find((run) => run.exitCode !== 0)?.output ?? "", 2e3) : void 0;
|
|
5383
5447
|
const lines = [
|
|
@@ -5394,9 +5458,9 @@ var diagnosticsTool = {
|
|
|
5394
5458
|
...byFile.length ? byFile.slice(0, input.maxFiles).map((entry) => `- ${entry.file}: ${entry.errors} error(s), ${entry.warnings} warning(s)`) : ["- none"],
|
|
5395
5459
|
byFile.length > input.maxFiles ? `- ...${byFile.length - input.maxFiles} more file(s)` : void 0,
|
|
5396
5460
|
"",
|
|
5397
|
-
|
|
5398
|
-
...
|
|
5399
|
-
!
|
|
5461
|
+
clean2 ? unparsed ? "No diagnostics parsed, but the checker exited non-zero. Raw output tail:" : "No diagnostics." : `Diagnostics (showing ${records.length} of ${matched.length}):`,
|
|
5462
|
+
...clean2 ? rawTail ? [rawTail] : [] : records.map(renderDiagnostic),
|
|
5463
|
+
!clean2 && matched.length > records.length ? `...${matched.length - records.length} more suppressed` : void 0
|
|
5400
5464
|
].filter((line) => line !== void 0);
|
|
5401
5465
|
return {
|
|
5402
5466
|
ok: !runs.some((run) => run.aborted) && (matched.length > 0 || runs.every((run) => run.exitCode === 0)),
|
|
@@ -5676,8 +5740,8 @@ function normalizeFilters(workspace, paths) {
|
|
|
5676
5740
|
}
|
|
5677
5741
|
function normalizeDiagnosticPath(workspace, diagnostic) {
|
|
5678
5742
|
try {
|
|
5679
|
-
const
|
|
5680
|
-
return { ...diagnostic, file:
|
|
5743
|
+
const relative3 = toWorkspaceRelative(workspace, resolveInsideWorkspace(workspace, diagnostic.file));
|
|
5744
|
+
return { ...diagnostic, file: relative3.replaceAll("\\", "/") };
|
|
5681
5745
|
} catch {
|
|
5682
5746
|
return { ...diagnostic, file: diagnostic.file.replaceAll("\\", "/") };
|
|
5683
5747
|
}
|
|
@@ -6090,6 +6154,21 @@ var listFilesTool = {
|
|
|
6090
6154
|
}
|
|
6091
6155
|
};
|
|
6092
6156
|
var RANGE_MARKER_RESERVE_CHARS = 300;
|
|
6157
|
+
async function resolveReadableFile(workspace, path) {
|
|
6158
|
+
try {
|
|
6159
|
+
const target = await resolveReadablePath(workspace, path);
|
|
6160
|
+
const fileStat = await stat4(target);
|
|
6161
|
+
if (!fileStat.isFile()) throw new Error(`Not a file: ${path}`);
|
|
6162
|
+
return { target, fileStat };
|
|
6163
|
+
} catch (error) {
|
|
6164
|
+
const code = error.code;
|
|
6165
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
6166
|
+
throw new Error(
|
|
6167
|
+
`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.`,
|
|
6168
|
+
{ cause: error }
|
|
6169
|
+
);
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6093
6172
|
async function readFilePrefix(target, maxBytes) {
|
|
6094
6173
|
const handle = await open3(target, "r");
|
|
6095
6174
|
try {
|
|
@@ -6148,11 +6227,7 @@ var readFileTool = {
|
|
|
6148
6227
|
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
6228
|
inputSchema: ReadFileInput,
|
|
6150
6229
|
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
|
-
}
|
|
6230
|
+
const { target } = await resolveReadableFile(context.workspace, input.path);
|
|
6156
6231
|
const { buffer: clipped, totalBytes, stable } = await readFilePrefix(target, input.maxBytes);
|
|
6157
6232
|
const text = clipped.toString("utf8");
|
|
6158
6233
|
const clippedByBytes = totalBytes > clipped.byteLength;
|
|
@@ -6248,11 +6323,7 @@ var readManyFilesTool = {
|
|
|
6248
6323
|
const sections = [];
|
|
6249
6324
|
let remainingBytes = input.maxTotalBytes;
|
|
6250
6325
|
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
|
-
}
|
|
6326
|
+
const { target, fileStat } = await resolveReadableFile(context.workspace, path);
|
|
6256
6327
|
const readBudget = Math.min(input.maxBytesPerFile, remainingBytes);
|
|
6257
6328
|
if (readBudget <= 0) {
|
|
6258
6329
|
files.push({ path, bytesRead: 0, totalBytes: fileStat.size, truncated: true });
|
|
@@ -6865,12 +6936,12 @@ var builtinSecretRules = [
|
|
|
6865
6936
|
];
|
|
6866
6937
|
async function resolveArtifact(workspace, requestedPath) {
|
|
6867
6938
|
const absolute = await resolveInsideWorkspaceReal(workspace, requestedPath);
|
|
6868
|
-
const
|
|
6939
|
+
const relative3 = toWorkspaceRelative(workspace, absolute);
|
|
6869
6940
|
const info = await stat5(absolute);
|
|
6870
6941
|
if (!info.isFile()) {
|
|
6871
6942
|
throw new Error(`Not a file: ${requestedPath}`);
|
|
6872
6943
|
}
|
|
6873
|
-
return { absolute, relative:
|
|
6944
|
+
return { absolute, relative: relative3 };
|
|
6874
6945
|
}
|
|
6875
6946
|
function formatMode(mode) {
|
|
6876
6947
|
return (mode & 511).toString(8).padStart(3, "0");
|
|
@@ -6970,8 +7041,8 @@ function shannonEntropy(value) {
|
|
|
6970
7041
|
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
6971
7042
|
}
|
|
6972
7043
|
let entropy = 0;
|
|
6973
|
-
for (const
|
|
6974
|
-
const probability =
|
|
7044
|
+
for (const count3 of counts.values()) {
|
|
7045
|
+
const probability = count3 / value.length;
|
|
6975
7046
|
entropy -= probability * Math.log2(probability);
|
|
6976
7047
|
}
|
|
6977
7048
|
return entropy;
|
|
@@ -8796,7 +8867,7 @@ function summarizeDirectories(files) {
|
|
|
8796
8867
|
const directory = dirname9(file.path).split(/[\\/]/)[0] || ".";
|
|
8797
8868
|
counts.set(directory, (counts.get(directory) ?? 0) + 1);
|
|
8798
8869
|
}
|
|
8799
|
-
return [...counts.entries()].map(([name,
|
|
8870
|
+
return [...counts.entries()].map(([name, count3]) => ({ name, count: count3 })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
8800
8871
|
}
|
|
8801
8872
|
function summarizeExtensions(files) {
|
|
8802
8873
|
const summaries = /* @__PURE__ */ new Map();
|
|
@@ -9349,18 +9420,18 @@ function isConcurrencySafe(tool) {
|
|
|
9349
9420
|
}
|
|
9350
9421
|
function planToolBatches(calls, tools) {
|
|
9351
9422
|
const batches = [];
|
|
9352
|
-
let
|
|
9423
|
+
let open11;
|
|
9353
9424
|
for (const call of calls) {
|
|
9354
9425
|
if (!isConcurrencySafe(tools.get(call.tool))) {
|
|
9355
9426
|
batches.push([call]);
|
|
9356
|
-
|
|
9427
|
+
open11 = void 0;
|
|
9357
9428
|
continue;
|
|
9358
9429
|
}
|
|
9359
|
-
if (!
|
|
9360
|
-
|
|
9361
|
-
batches.push(
|
|
9430
|
+
if (!open11) {
|
|
9431
|
+
open11 = [];
|
|
9432
|
+
batches.push(open11);
|
|
9362
9433
|
}
|
|
9363
|
-
|
|
9434
|
+
open11.push(call);
|
|
9364
9435
|
}
|
|
9365
9436
|
return batches;
|
|
9366
9437
|
}
|
|
@@ -9855,7 +9926,7 @@ async function acquireLock(directory, waitMs) {
|
|
|
9855
9926
|
};
|
|
9856
9927
|
} catch (error) {
|
|
9857
9928
|
if (error.code !== "EEXIST" || Date.now() >= deadline) throw error;
|
|
9858
|
-
await new Promise((
|
|
9929
|
+
await new Promise((resolve17) => setTimeout(resolve17, 10));
|
|
9859
9930
|
}
|
|
9860
9931
|
}
|
|
9861
9932
|
}
|
|
@@ -9903,6 +9974,8 @@ function readSessionContextSnapshot(session) {
|
|
|
9903
9974
|
const snapshot = session.contextSnapshot;
|
|
9904
9975
|
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
9976
|
if (!message || typeof message !== "object" || !["user", "assistant", "system", "tool"].includes(message.role) || typeof message.content !== "string") return true;
|
|
9977
|
+
const image = message.image;
|
|
9978
|
+
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
9979
|
const native = message;
|
|
9907
9980
|
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
9981
|
}) || !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 +11217,10 @@ async function withHardTimeout(promise, ms, onTimeout) {
|
|
|
11144
11217
|
(error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })
|
|
11145
11218
|
);
|
|
11146
11219
|
let timer;
|
|
11147
|
-
const expiry = new Promise((
|
|
11220
|
+
const expiry = new Promise((resolve17) => {
|
|
11148
11221
|
timer = setTimeout(() => {
|
|
11149
11222
|
onTimeout?.();
|
|
11150
|
-
|
|
11223
|
+
resolve17(void 0);
|
|
11151
11224
|
}, ms);
|
|
11152
11225
|
});
|
|
11153
11226
|
try {
|
|
@@ -12304,8 +12377,8 @@ function findFileInText(text, workspace) {
|
|
|
12304
12377
|
for (const match of text.matchAll(stackFrame)) {
|
|
12305
12378
|
const candidate = match[1];
|
|
12306
12379
|
if (candidate.includes("node_modules") || candidate.includes("/internal/")) continue;
|
|
12307
|
-
const
|
|
12308
|
-
if (
|
|
12380
|
+
const relative3 = relativizeFile(candidate, workspace);
|
|
12381
|
+
if (relative3 !== "unknown") return relative3;
|
|
12309
12382
|
}
|
|
12310
12383
|
return null;
|
|
12311
12384
|
}
|
|
@@ -12793,12 +12866,12 @@ async function sendRequest(url, input, approved, signal) {
|
|
|
12793
12866
|
// The socket goes to a pinned address; TLS still verifies the name.
|
|
12794
12867
|
servername: secure && !isIP(hostname) ? hostname : void 0
|
|
12795
12868
|
};
|
|
12796
|
-
return await new Promise((
|
|
12869
|
+
return await new Promise((resolve17, reject) => {
|
|
12797
12870
|
const request = secure ? httpsRequest(options) : httpRequest(options);
|
|
12798
12871
|
request.on("response", (response) => {
|
|
12799
12872
|
response.on("error", () => {
|
|
12800
12873
|
});
|
|
12801
|
-
|
|
12874
|
+
resolve17({
|
|
12802
12875
|
status: response.statusCode ?? 0,
|
|
12803
12876
|
statusText: response.statusMessage ?? "",
|
|
12804
12877
|
ok: (response.statusCode ?? 0) >= 200 && (response.statusCode ?? 0) < 300,
|
|
@@ -13277,8 +13350,8 @@ var LOOPBACK_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
|
13277
13350
|
async function startLoopbackServer(options) {
|
|
13278
13351
|
const timeoutMs = options.timeoutMs ?? LOOPBACK_TIMEOUT_MS;
|
|
13279
13352
|
let settle;
|
|
13280
|
-
const wait = new Promise((
|
|
13281
|
-
settle =
|
|
13353
|
+
const wait = new Promise((resolve17) => {
|
|
13354
|
+
settle = resolve17;
|
|
13282
13355
|
});
|
|
13283
13356
|
const server = http.createServer((request, response) => {
|
|
13284
13357
|
const host = request.headers.host ?? "127.0.0.1";
|
|
@@ -13325,10 +13398,10 @@ async function startLoopbackServer(options) {
|
|
|
13325
13398
|
settle?.({ ok: false, error: "server_error", description: error.message });
|
|
13326
13399
|
settle = void 0;
|
|
13327
13400
|
});
|
|
13328
|
-
const port = await new Promise((
|
|
13401
|
+
const port = await new Promise((resolve17, reject) => {
|
|
13329
13402
|
server.listen(0, "127.0.0.1", () => {
|
|
13330
13403
|
const address = server.address();
|
|
13331
|
-
if (address && typeof address === "object")
|
|
13404
|
+
if (address && typeof address === "object") resolve17(address.port);
|
|
13332
13405
|
else reject(new Error("loopback server did not bind a port"));
|
|
13333
13406
|
});
|
|
13334
13407
|
server.once("error", reject);
|
|
@@ -13347,9 +13420,9 @@ async function startLoopbackServer(options) {
|
|
|
13347
13420
|
port,
|
|
13348
13421
|
redirectUri: `http://127.0.0.1:${port}${LOOPBACK_PATH}`,
|
|
13349
13422
|
wait,
|
|
13350
|
-
close: () => new Promise((
|
|
13423
|
+
close: () => new Promise((resolve17) => {
|
|
13351
13424
|
clearTimeout(timer);
|
|
13352
|
-
server.close(() =>
|
|
13425
|
+
server.close(() => resolve17());
|
|
13353
13426
|
})
|
|
13354
13427
|
};
|
|
13355
13428
|
}
|
|
@@ -13463,6 +13536,22 @@ function errorMessage(error) {
|
|
|
13463
13536
|
}
|
|
13464
13537
|
return String(error);
|
|
13465
13538
|
}
|
|
13539
|
+
async function readAccountPlan(credentials, fetchImpl = (input, init) => fetch(input, init)) {
|
|
13540
|
+
if (!credentials) return void 0;
|
|
13541
|
+
try {
|
|
13542
|
+
const response = await fetchImpl(modelsUrl(credentials.baseUrl), {
|
|
13543
|
+
headers: { authorization: `Bearer ${credentials.apiKey}`, accept: "application/json" },
|
|
13544
|
+
redirect: "error",
|
|
13545
|
+
signal: AbortSignal.timeout(2e3)
|
|
13546
|
+
});
|
|
13547
|
+
if (!response.ok) return void 0;
|
|
13548
|
+
const body = await response.json();
|
|
13549
|
+
const plans = { free: "Free", pro: "Pro", team: "Team", ultra: "Ultra" };
|
|
13550
|
+
return typeof body.account?.plan === "string" && Object.hasOwn(plans, body.account.plan) ? plans[body.account.plan] : void 0;
|
|
13551
|
+
} catch {
|
|
13552
|
+
return void 0;
|
|
13553
|
+
}
|
|
13554
|
+
}
|
|
13466
13555
|
|
|
13467
13556
|
// src/auth/browser-login.ts
|
|
13468
13557
|
var BROWSER_LOGIN_REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -13660,7 +13749,7 @@ function silenceTimeoutMs() {
|
|
|
13660
13749
|
return Number.isFinite(override) && override > 0 ? override : DEFAULT_NON_TTY_SILENCE_TIMEOUT_MS;
|
|
13661
13750
|
}
|
|
13662
13751
|
function ask(rl, question, onSettle) {
|
|
13663
|
-
return new Promise((
|
|
13752
|
+
return new Promise((resolve17) => {
|
|
13664
13753
|
let settled = false;
|
|
13665
13754
|
let silenceTimer;
|
|
13666
13755
|
const keepAwake = () => {
|
|
@@ -13674,7 +13763,7 @@ function ask(rl, question, onSettle) {
|
|
|
13674
13763
|
stdin2.off("data", keepAwake);
|
|
13675
13764
|
rl.close();
|
|
13676
13765
|
onSettle?.();
|
|
13677
|
-
|
|
13766
|
+
resolve17(value.trim());
|
|
13678
13767
|
};
|
|
13679
13768
|
if (!stdin2.isTTY) {
|
|
13680
13769
|
silenceTimer = setTimeout(() => finish(""), silenceTimeoutMs());
|
|
@@ -14026,9 +14115,9 @@ var JsonRpcEndpoint = class {
|
|
|
14026
14115
|
if (signal?.aborted) {
|
|
14027
14116
|
return Promise.reject(new JsonRpcRequestAbortedError(method, void 0, false));
|
|
14028
14117
|
}
|
|
14029
|
-
return new Promise((
|
|
14118
|
+
return new Promise((resolve17, reject) => {
|
|
14030
14119
|
const limit = timeoutMs ?? this.defaultTimeoutMs;
|
|
14031
|
-
const entry = { method, resolve:
|
|
14120
|
+
const entry = { method, resolve: resolve17, reject, signal, dispatched: false };
|
|
14032
14121
|
if (signal) {
|
|
14033
14122
|
entry.abortHandler = () => {
|
|
14034
14123
|
this.settle(id, () => reject(new JsonRpcRequestAbortedError(method, id, entry.dispatched)));
|
|
@@ -14711,13 +14800,13 @@ function renderMcpStatuses(statuses) {
|
|
|
14711
14800
|
}).join("\n");
|
|
14712
14801
|
}
|
|
14713
14802
|
function withTimeout(promise, ms, message) {
|
|
14714
|
-
return new Promise((
|
|
14803
|
+
return new Promise((resolve17, reject) => {
|
|
14715
14804
|
const timer = setTimeout(() => reject(new Error(`${message} within ${ms}ms.`)), ms);
|
|
14716
14805
|
timer.unref?.();
|
|
14717
14806
|
promise.then(
|
|
14718
14807
|
(value) => {
|
|
14719
14808
|
clearTimeout(timer);
|
|
14720
|
-
|
|
14809
|
+
resolve17(value);
|
|
14721
14810
|
},
|
|
14722
14811
|
(error) => {
|
|
14723
14812
|
clearTimeout(timer);
|
|
@@ -15116,14 +15205,14 @@ function redact(secret) {
|
|
|
15116
15205
|
// src/notify/channel.ts
|
|
15117
15206
|
function sleep(ms, signal) {
|
|
15118
15207
|
if (signal?.aborted) return Promise.resolve(false);
|
|
15119
|
-
return new Promise((
|
|
15208
|
+
return new Promise((resolve17) => {
|
|
15120
15209
|
const timer = setTimeout(() => {
|
|
15121
15210
|
signal?.removeEventListener("abort", onAbort);
|
|
15122
|
-
|
|
15211
|
+
resolve17(true);
|
|
15123
15212
|
}, ms);
|
|
15124
15213
|
const onAbort = () => {
|
|
15125
15214
|
clearTimeout(timer);
|
|
15126
|
-
|
|
15215
|
+
resolve17(false);
|
|
15127
15216
|
};
|
|
15128
15217
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15129
15218
|
});
|
|
@@ -15371,7 +15460,7 @@ ${question}${optionLines}`, signal);
|
|
|
15371
15460
|
await Promise.allSettled(this.channels.map((channel) => channel.call?.(spoken, wait.signal)));
|
|
15372
15461
|
})();
|
|
15373
15462
|
const timeout = sleep(this.timing.timeoutMs, wait.signal).then(() => null);
|
|
15374
|
-
const cancelled = new Promise((
|
|
15463
|
+
const cancelled = new Promise((resolve17) => wait.signal.addEventListener("abort", () => resolve17(null), { once: true }));
|
|
15375
15464
|
const reply = await Promise.race([...replies, timeout, cancelled]);
|
|
15376
15465
|
return reply === null ? null : pickOption(reply, options);
|
|
15377
15466
|
} finally {
|
|
@@ -15682,6 +15771,80 @@ function registerTrustCommand(program2) {
|
|
|
15682
15771
|
});
|
|
15683
15772
|
}
|
|
15684
15773
|
|
|
15774
|
+
// src/core/image-attachments.ts
|
|
15775
|
+
import { constants as constants6 } from "fs";
|
|
15776
|
+
import { open as open9 } from "fs/promises";
|
|
15777
|
+
import { homedir as homedir2 } from "os";
|
|
15778
|
+
import { isAbsolute as isAbsolute4 } from "path";
|
|
15779
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15780
|
+
import sharp from "sharp";
|
|
15781
|
+
var MAX_SOURCE_BYTES = 20 * 1024 * 1024;
|
|
15782
|
+
function imageDrops(text) {
|
|
15783
|
+
const drops = [];
|
|
15784
|
+
const tokens = /"(?:\\.|[^"\\])*"|'[^']*'|(?:\\.|[^\s])+/g;
|
|
15785
|
+
for (const match of text.matchAll(tokens)) {
|
|
15786
|
+
const token = match[0];
|
|
15787
|
+
let path = token.startsWith("'") ? token.slice(1, -1) : token.startsWith('"') ? token.slice(1, -1).replace(/\\([\\"$`])/g, "$1") : token.replace(/\\(.)/g, "$1");
|
|
15788
|
+
if (path.startsWith("file://")) {
|
|
15789
|
+
try {
|
|
15790
|
+
path = fileURLToPath2(path);
|
|
15791
|
+
} catch {
|
|
15792
|
+
continue;
|
|
15793
|
+
}
|
|
15794
|
+
}
|
|
15795
|
+
if (path.startsWith("~/")) path = `${homedir2()}/${path.slice(2)}`;
|
|
15796
|
+
if (!isAbsolute4(path) || !/\.(png|jpe?g|webp)$/i.test(path)) continue;
|
|
15797
|
+
drops.push({ start: match.index, end: match.index + token.length, path, label: `[Image ${drops.length + 1}]` });
|
|
15798
|
+
}
|
|
15799
|
+
return drops;
|
|
15800
|
+
}
|
|
15801
|
+
function formatImageInput(text, cursor = text.length) {
|
|
15802
|
+
let display = "";
|
|
15803
|
+
let previous = 0;
|
|
15804
|
+
let shift = 0;
|
|
15805
|
+
let adjusted = cursor;
|
|
15806
|
+
for (const drop of imageDrops(text)) {
|
|
15807
|
+
display += text.slice(previous, drop.start) + drop.label;
|
|
15808
|
+
if (cursor >= drop.end) adjusted += drop.label.length - (drop.end - drop.start);
|
|
15809
|
+
else if (cursor > drop.start) adjusted = drop.start + shift + Math.min(drop.label.length, cursor - drop.start);
|
|
15810
|
+
shift += drop.label.length - (drop.end - drop.start);
|
|
15811
|
+
previous = drop.end;
|
|
15812
|
+
}
|
|
15813
|
+
return { text: display + text.slice(previous), cursor: Math.max(0, adjusted) };
|
|
15814
|
+
}
|
|
15815
|
+
function hasDroppedImage(text) {
|
|
15816
|
+
return imageDrops(text).length > 0;
|
|
15817
|
+
}
|
|
15818
|
+
async function prepareImageInput(text) {
|
|
15819
|
+
const drops = imageDrops(text);
|
|
15820
|
+
if (!drops.length) return { text };
|
|
15821
|
+
if (drops.length > 1) throw new Error("Attach one image per message. Cosmic currently accepts one image in its active context.");
|
|
15822
|
+
const drop = drops[0];
|
|
15823
|
+
const file = await open9(drop.path, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
|
|
15824
|
+
let bytes;
|
|
15825
|
+
try {
|
|
15826
|
+
const stat11 = await file.stat();
|
|
15827
|
+
if (!stat11.isFile() || stat11.size > MAX_SOURCE_BYTES) throw new Error("Image must be a regular file no larger than 20 MiB.");
|
|
15828
|
+
const buffer = Buffer.alloc(Math.min(stat11.size, MAX_SOURCE_BYTES) + 1);
|
|
15829
|
+
let used = 0;
|
|
15830
|
+
while (used < buffer.length) {
|
|
15831
|
+
const read = await file.read(buffer, used, buffer.length - used, null);
|
|
15832
|
+
if (!read.bytesRead) break;
|
|
15833
|
+
used += read.bytesRead;
|
|
15834
|
+
}
|
|
15835
|
+
if (used > MAX_SOURCE_BYTES) throw new Error("Image exceeds 20 MiB.");
|
|
15836
|
+
bytes = buffer.subarray(0, used);
|
|
15837
|
+
} finally {
|
|
15838
|
+
await file.close();
|
|
15839
|
+
}
|
|
15840
|
+
const image = sharp(bytes, { limitInputPixels: 4e7, animated: false });
|
|
15841
|
+
const metadata = await image.metadata();
|
|
15842
|
+
if (!["png", "jpeg", "webp"].includes(metadata.format ?? "") || (metadata.pages ?? 1) > 1) throw new Error("Attach a single PNG, JPEG or WebP image.");
|
|
15843
|
+
const normalized = await image.rotate().resize({ width: 1024, height: 1024, fit: "inside", withoutEnlargement: true }).png().toBuffer();
|
|
15844
|
+
if (normalized.length > 5 * 1024 * 1024) throw new Error("Normalized image exceeds the upload limit.");
|
|
15845
|
+
return { text: formatImageInput(text).text, image: { url: `data:image/png;base64,${normalized.toString("base64")}` } };
|
|
15846
|
+
}
|
|
15847
|
+
|
|
15685
15848
|
// src/core/manual-compaction.ts
|
|
15686
15849
|
async function compactManually(messages, pinned, provider, model, focus = "", signal) {
|
|
15687
15850
|
const retained = new Set(pinned);
|
|
@@ -15959,7 +16122,8 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
15959
16122
|
async ask(input, handlers = {}) {
|
|
15960
16123
|
if (!this.started) await this.start();
|
|
15961
16124
|
if (!this.firstTask) this.firstTask = input;
|
|
15962
|
-
|
|
16125
|
+
if (handlers.image) for (const message of this.messages) delete message.image;
|
|
16126
|
+
const asked = { role: "user", content: input, ...handlers.image ? { image: handlers.image } : {} };
|
|
15963
16127
|
this.messages.push(asked);
|
|
15964
16128
|
this.pinnedMessages.push(asked);
|
|
15965
16129
|
this.userConstraints.push({ kind: "message", content: input });
|
|
@@ -16275,7 +16439,7 @@ Acknowledge that you have the context and wait for the user's first request.`
|
|
|
16275
16439
|
constraintVersion: 1,
|
|
16276
16440
|
userConstraints: [...this.userConstraints],
|
|
16277
16441
|
mcpResultArchiveVersion: 1,
|
|
16278
|
-
...this.messages.some(isCompactionSummary) ? { contextSnapshot: { version: 1, kind: "text", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16442
|
+
...this.messages.some((message) => isCompactionSummary(message) || message.image) ? { contextSnapshot: { version: 1, kind: "text", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
16279
16443
|
// How the last turn ended, so a saved interactive transcript says whether
|
|
16280
16444
|
// its final message is an answer or the place it ran out of room - the
|
|
16281
16445
|
// same question `ultrac run` already answers for a one-shot run.
|
|
@@ -16490,12 +16654,18 @@ ${context}`
|
|
|
16490
16654
|
async ask(input, handlers = {}) {
|
|
16491
16655
|
if (!this.started) await this.start();
|
|
16492
16656
|
if (!this.firstTask) this.firstTask = input;
|
|
16493
|
-
|
|
16657
|
+
if (handlers.image) {
|
|
16658
|
+
for (const message of this.messages) if (message.role === "user" || message.role === "system") delete message.image;
|
|
16659
|
+
}
|
|
16660
|
+
const asked = { role: "user", content: input, ...handlers.image ? { image: handlers.image } : {} };
|
|
16494
16661
|
this.messages.push(asked);
|
|
16495
16662
|
this.pinnedMessages.push(asked);
|
|
16496
16663
|
this.userConstraints.push({ kind: "message", content: input });
|
|
16497
16664
|
let finalMessage = "";
|
|
16498
16665
|
let toolCallsUsed = 0;
|
|
16666
|
+
let consecutiveEmptyResponses = 0;
|
|
16667
|
+
const turnStarted = performance.now();
|
|
16668
|
+
const firstStep = this.steps.length;
|
|
16499
16669
|
let stopReason = "max-steps";
|
|
16500
16670
|
try {
|
|
16501
16671
|
for (let index = 0; index < this.options.maxSteps; index += 1) {
|
|
@@ -16548,6 +16718,7 @@ ${context}`
|
|
|
16548
16718
|
throw budgetError;
|
|
16549
16719
|
}
|
|
16550
16720
|
toolCallsUsed += response.toolCalls.length;
|
|
16721
|
+
if (response.content.trim() || response.toolCalls.length) consecutiveEmptyResponses = 0;
|
|
16551
16722
|
if (response.content.trim()) {
|
|
16552
16723
|
finalMessage = response.content;
|
|
16553
16724
|
handlers.onAssistantMessage?.(response.content);
|
|
@@ -16567,6 +16738,21 @@ ${context}`
|
|
|
16567
16738
|
handlers.onInterrupted?.();
|
|
16568
16739
|
break;
|
|
16569
16740
|
}
|
|
16741
|
+
if (!response.content.trim()) {
|
|
16742
|
+
consecutiveEmptyResponses += 1;
|
|
16743
|
+
const willRetry = consecutiveEmptyResponses <= 2 && index + 1 < this.options.maxSteps;
|
|
16744
|
+
diagnosticEvent("session.empty-response", {
|
|
16745
|
+
sessionId: this.id,
|
|
16746
|
+
attempt: consecutiveEmptyResponses,
|
|
16747
|
+
willRetry,
|
|
16748
|
+
maxRecoveries: 2,
|
|
16749
|
+
stepCount: this.steps.length
|
|
16750
|
+
});
|
|
16751
|
+
if (!willRetry) {
|
|
16752
|
+
throw Object.assign(new Error("The model returned an empty response without completing the task. Empty-response recovery was exhausted; the session checkpoint is preserved."), { status: 422 });
|
|
16753
|
+
}
|
|
16754
|
+
continue;
|
|
16755
|
+
}
|
|
16570
16756
|
if (this.appendQueuedMessages(handlers) === 0) {
|
|
16571
16757
|
stopReason = "completed";
|
|
16572
16758
|
break;
|
|
@@ -16634,6 +16820,15 @@ ${context}`
|
|
|
16634
16820
|
throw error;
|
|
16635
16821
|
} finally {
|
|
16636
16822
|
this.lastStopReason = stopReason;
|
|
16823
|
+
diagnosticEvent("session.turn.end", {
|
|
16824
|
+
sessionId: this.id,
|
|
16825
|
+
reason: stopReason,
|
|
16826
|
+
stepCount: this.steps.length,
|
|
16827
|
+
turnSteps: this.steps.length - firstStep,
|
|
16828
|
+
maxSteps: this.options.maxSteps,
|
|
16829
|
+
emptyResponses: consecutiveEmptyResponses,
|
|
16830
|
+
durationMs: Math.max(0, performance.now() - turnStarted)
|
|
16831
|
+
});
|
|
16637
16832
|
this.subagents.push(...drainSubagentTranscripts());
|
|
16638
16833
|
}
|
|
16639
16834
|
handlers.onStopped?.(stopReason);
|
|
@@ -16840,7 +17035,7 @@ ${guidance.join("\n")}` : ""}`;
|
|
|
16840
17035
|
constraintVersion: 1,
|
|
16841
17036
|
userConstraints: [...this.userConstraints],
|
|
16842
17037
|
mcpResultArchiveVersion: 1,
|
|
16843
|
-
...this.messages.some(isCompactionSummary) ? { contextSnapshot: { version: 1, kind: "native", messages: snapshotWithConstraints(this.messages, this.userConstraints), completedSteps: this.steps.length } } : {},
|
|
17038
|
+
...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
17039
|
// How the last turn ended, so a saved interactive transcript says whether
|
|
16845
17040
|
// its final message is an answer or the place it ran out of room - the
|
|
16846
17041
|
// same question `ultrac run` already answers for a one-shot run.
|
|
@@ -16882,10 +17077,10 @@ function boundedInteger(value, name, min, max) {
|
|
|
16882
17077
|
}
|
|
16883
17078
|
function abortableSleep(milliseconds, signal) {
|
|
16884
17079
|
signal.throwIfAborted();
|
|
16885
|
-
return new Promise((
|
|
17080
|
+
return new Promise((resolve17, reject) => {
|
|
16886
17081
|
const timer = setTimeout(() => {
|
|
16887
17082
|
signal.removeEventListener("abort", abort);
|
|
16888
|
-
|
|
17083
|
+
resolve17();
|
|
16889
17084
|
}, milliseconds);
|
|
16890
17085
|
const abort = () => {
|
|
16891
17086
|
clearTimeout(timer);
|
|
@@ -16937,6 +17132,11 @@ async function runAutonomousLoop(options) {
|
|
|
16937
17132
|
let lastStepCount = 0;
|
|
16938
17133
|
let stalls = 0;
|
|
16939
17134
|
let consecutiveErrors = 0;
|
|
17135
|
+
const finish = (session, rounds, haltReason) => {
|
|
17136
|
+
diagnosticEvent("autonomous.end", { id, sessionId: session.id, rounds, reason: haltReason, elapsedMs: elapsed() });
|
|
17137
|
+
return { session, rounds, haltReason };
|
|
17138
|
+
};
|
|
17139
|
+
diagnosticEvent("autonomous.start", { id, maxRounds: options.maxRounds, maxElapsedMs });
|
|
16940
17140
|
try {
|
|
16941
17141
|
signal.throwIfAborted();
|
|
16942
17142
|
for (let round = 1; ; round += 1) {
|
|
@@ -16967,8 +17167,8 @@ async function runAutonomousLoop(options) {
|
|
|
16967
17167
|
else if (!shouldResume(session.stopReason)) haltReason = "stopped";
|
|
16968
17168
|
else if (stalls >= stallLimit) haltReason = "stalled";
|
|
16969
17169
|
else if (round >= options.maxRounds) haltReason = "max_rounds";
|
|
16970
|
-
const
|
|
16971
|
-
if (!haltReason && session.stopReason === "error" && elapsed() +
|
|
17170
|
+
const retryDelay2 = Math.min(maxDelay, baseDelay * 2 ** Math.min(30, consecutiveErrors - 1));
|
|
17171
|
+
if (!haltReason && session.stopReason === "error" && elapsed() + retryDelay2 >= maxElapsedMs) haltReason = "elapsed_budget";
|
|
16972
17172
|
diagnosticEvent("round.end", {
|
|
16973
17173
|
id,
|
|
16974
17174
|
round,
|
|
@@ -16982,10 +17182,10 @@ async function runAutonomousLoop(options) {
|
|
|
16982
17182
|
});
|
|
16983
17183
|
if (haltReason === "stalled") diagnosticEvent("round.stall", { id, round, repeats: stalls, completedTools: current.completedTools, elapsedMs: elapsed() });
|
|
16984
17184
|
await options.onRoundEnd?.(round, session, !haltReason);
|
|
16985
|
-
if (haltReason) return
|
|
17185
|
+
if (haltReason) return finish(session, round, haltReason);
|
|
16986
17186
|
if (session.stopReason === "error") {
|
|
16987
|
-
const delayMs =
|
|
16988
|
-
if (elapsed() + delayMs >= maxElapsedMs) return
|
|
17187
|
+
const delayMs = retryDelay2;
|
|
17188
|
+
if (elapsed() + delayMs >= maxElapsedMs) return finish(session, round, "elapsed_budget");
|
|
16989
17189
|
diagnosticEvent("round.retry", {
|
|
16990
17190
|
id,
|
|
16991
17191
|
round,
|
|
@@ -16999,12 +17199,15 @@ async function runAutonomousLoop(options) {
|
|
|
16999
17199
|
await sleep2(delayMs, signal);
|
|
17000
17200
|
} catch (error) {
|
|
17001
17201
|
if (!signal.aborted) throw error;
|
|
17002
|
-
return
|
|
17202
|
+
return finish(session, round, budget.signal.aborted ? "elapsed_budget" : "aborted");
|
|
17003
17203
|
}
|
|
17004
17204
|
}
|
|
17005
|
-
if (signal.aborted || elapsed() >= maxElapsedMs) return
|
|
17205
|
+
if (signal.aborted || elapsed() >= maxElapsedMs) return finish(session, round, budget.signal.aborted || elapsed() >= maxElapsedMs ? "elapsed_budget" : "aborted");
|
|
17006
17206
|
previous = session;
|
|
17007
17207
|
}
|
|
17208
|
+
} catch (error) {
|
|
17209
|
+
diagnosticEvent("autonomous.end", { id, reason: budget.signal.aborted ? "elapsed_budget" : signal.aborted ? "aborted" : "exception", elapsedMs: elapsed() });
|
|
17210
|
+
throw error;
|
|
17008
17211
|
} finally {
|
|
17009
17212
|
clearTimeout(deadline);
|
|
17010
17213
|
}
|
|
@@ -17271,53 +17474,8 @@ function attachProgressUi(options = {}) {
|
|
|
17271
17474
|
};
|
|
17272
17475
|
}
|
|
17273
17476
|
|
|
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
|
-
};
|
|
17477
|
+
// src/ui/activity.ts
|
|
17478
|
+
import { stripVTControlCharacters } from "util";
|
|
17321
17479
|
|
|
17322
17480
|
// src/ui/tool-activity.ts
|
|
17323
17481
|
var VERBS = {
|
|
@@ -17337,7 +17495,9 @@ var VERBS = {
|
|
|
17337
17495
|
run_tests: "Run tests",
|
|
17338
17496
|
diagnostics: "Check diagnostics",
|
|
17339
17497
|
task: "Delegate",
|
|
17340
|
-
fetch_url: "
|
|
17498
|
+
fetch_url: "Opened",
|
|
17499
|
+
web_search: "Searched the web for",
|
|
17500
|
+
search_web: "Searched the web for",
|
|
17341
17501
|
git_status: "Check Git status",
|
|
17342
17502
|
git_diff: "Read Git diff",
|
|
17343
17503
|
git_log: "Read Git log",
|
|
@@ -17365,7 +17525,7 @@ function toolTarget(call) {
|
|
|
17365
17525
|
if (paths.length > 1) return `${paths.length} files`;
|
|
17366
17526
|
if (call.tool === "run_shell") return compact2(stringValue(input.command), 72);
|
|
17367
17527
|
if (call.tool === "run_package_script") return compact2(stringValue(input.script), 50);
|
|
17368
|
-
if (
|
|
17528
|
+
if (["search_files", "web_search", "search_web"].includes(call.tool)) return quote(stringValue(input.query) || stringValue(input.pattern));
|
|
17369
17529
|
if (call.tool === "fetch_url") return compact2(stringValue(input.url), 72);
|
|
17370
17530
|
if (call.tool === "task") return compact2(stringValue(input.description) || stringValue(input.task), 64);
|
|
17371
17531
|
if (call.tool === "git_show") return compact2(stringValue(input.ref), 40);
|
|
@@ -17390,14 +17550,180 @@ function humanize(value) {
|
|
|
17390
17550
|
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : "Tool";
|
|
17391
17551
|
}
|
|
17392
17552
|
|
|
17553
|
+
// src/ui/spinner.ts
|
|
17554
|
+
import { stdout as stdout4 } from "process";
|
|
17555
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17556
|
+
var Spinner = class {
|
|
17557
|
+
constructor(label = "working", renderLine) {
|
|
17558
|
+
this.label = label;
|
|
17559
|
+
this.renderLine = renderLine;
|
|
17560
|
+
}
|
|
17561
|
+
label;
|
|
17562
|
+
renderLine;
|
|
17563
|
+
timer;
|
|
17564
|
+
index = 0;
|
|
17565
|
+
holdWhile;
|
|
17566
|
+
held = false;
|
|
17567
|
+
startedAt;
|
|
17568
|
+
start(holdWhile, startedAt = performance.now()) {
|
|
17569
|
+
this.holdWhile = holdWhile;
|
|
17570
|
+
if (this.startedAt !== void 0) return;
|
|
17571
|
+
this.startedAt = startedAt;
|
|
17572
|
+
this.held = false;
|
|
17573
|
+
if (!stdout4.isTTY) return;
|
|
17574
|
+
this.render();
|
|
17575
|
+
this.timer = setInterval(() => this.render(), 90);
|
|
17576
|
+
}
|
|
17577
|
+
update(label) {
|
|
17578
|
+
this.label = label;
|
|
17579
|
+
}
|
|
17580
|
+
stop(completion) {
|
|
17581
|
+
const elapsed = this.startedAt === void 0 ? void 0 : performance.now() - this.startedAt;
|
|
17582
|
+
this.startedAt = void 0;
|
|
17583
|
+
if (this.timer) {
|
|
17584
|
+
clearInterval(this.timer);
|
|
17585
|
+
this.timer = void 0;
|
|
17586
|
+
}
|
|
17587
|
+
if (stdout4.isTTY && elapsed !== void 0) {
|
|
17588
|
+
if (this.renderLine) this.renderLine("");
|
|
17589
|
+
else {
|
|
17590
|
+
stdout4.clearLine(0);
|
|
17591
|
+
stdout4.cursorTo(0);
|
|
17592
|
+
}
|
|
17593
|
+
if (completion && elapsed >= 1e3) stdout4.write(` ${completion} ${formatSeconds(elapsed)}.
|
|
17594
|
+
`);
|
|
17595
|
+
}
|
|
17596
|
+
}
|
|
17597
|
+
render() {
|
|
17598
|
+
if (this.holdWhile?.()) {
|
|
17599
|
+
if (!this.held) {
|
|
17600
|
+
if (this.renderLine) this.renderLine("");
|
|
17601
|
+
else {
|
|
17602
|
+
stdout4.clearLine(0);
|
|
17603
|
+
stdout4.cursorTo(0);
|
|
17604
|
+
}
|
|
17605
|
+
this.held = true;
|
|
17606
|
+
}
|
|
17607
|
+
return;
|
|
17608
|
+
}
|
|
17609
|
+
this.held = false;
|
|
17610
|
+
this.index = (this.index + 1) % FRAMES.length;
|
|
17611
|
+
if (!this.renderLine) {
|
|
17612
|
+
stdout4.clearLine(0);
|
|
17613
|
+
stdout4.cursorTo(0);
|
|
17614
|
+
}
|
|
17615
|
+
const milliseconds = performance.now() - (this.startedAt ?? performance.now());
|
|
17616
|
+
const elapsed = milliseconds >= 1e3 ? formatElapsed(milliseconds) : "";
|
|
17617
|
+
const limit = Math.max(0, (stdout4.columns ?? 80) - elapsed.length - 10);
|
|
17618
|
+
let label = "";
|
|
17619
|
+
let cells2 = 0;
|
|
17620
|
+
for (const character of this.label.replace(/[\x00-\x1f\x7f]/g, " ")) {
|
|
17621
|
+
const size = character.codePointAt(0) > 127 ? 2 : 1;
|
|
17622
|
+
if (cells2 + size > limit) break;
|
|
17623
|
+
label += character;
|
|
17624
|
+
cells2 += size;
|
|
17625
|
+
}
|
|
17626
|
+
const line = ` ${FRAMES[this.index]} ${label}\u2026${elapsed ? ` \xB7 ${elapsed}` : ""}`;
|
|
17627
|
+
if (this.renderLine) this.renderLine(line);
|
|
17628
|
+
else stdout4.write(line);
|
|
17629
|
+
}
|
|
17630
|
+
};
|
|
17631
|
+
function formatSeconds(milliseconds) {
|
|
17632
|
+
const seconds = Math.max(0, milliseconds) / 1e3;
|
|
17633
|
+
const count3 = seconds.toFixed(1).replace(/\.0$/, "");
|
|
17634
|
+
return `${count3} ${count3 === "1" ? "second" : "seconds"}`;
|
|
17635
|
+
}
|
|
17636
|
+
|
|
17637
|
+
// src/ui/activity.ts
|
|
17638
|
+
var sink;
|
|
17639
|
+
var workingSink;
|
|
17640
|
+
function setWorkingSink(value) {
|
|
17641
|
+
workingSink = value;
|
|
17642
|
+
}
|
|
17643
|
+
function getWorkingSink() {
|
|
17644
|
+
return workingSink;
|
|
17645
|
+
}
|
|
17646
|
+
function setActivitySink(value) {
|
|
17647
|
+
sink = value;
|
|
17648
|
+
}
|
|
17649
|
+
function getActivitySink() {
|
|
17650
|
+
return sink;
|
|
17651
|
+
}
|
|
17652
|
+
var clean = (text) => stripVTControlCharacters(text).replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
17653
|
+
function activityEntry(call, result, durationMs) {
|
|
17654
|
+
const fields = ["command", "script", "path", "paths", "query", "pattern", "url", "cwd"];
|
|
17655
|
+
const request = fields.flatMap((key) => {
|
|
17656
|
+
const value = call.input[key];
|
|
17657
|
+
return typeof value === "string" ? [`${key}: ${value}`] : Array.isArray(value) && value.every((item) => typeof item === "string") ? [`${key}: ${value.join(", ")}`] : [];
|
|
17658
|
+
}).join(" \xB7 ");
|
|
17659
|
+
return {
|
|
17660
|
+
label: clean(toolActivityLabel(call)),
|
|
17661
|
+
request: clean(request).slice(0, 4e3),
|
|
17662
|
+
output: clean(result.content).slice(0, 1e3),
|
|
17663
|
+
ok: result.ok,
|
|
17664
|
+
durationMs
|
|
17665
|
+
};
|
|
17666
|
+
}
|
|
17667
|
+
function activityRows(snapshot, expanded, selected, end) {
|
|
17668
|
+
const failures = snapshot.entries.filter((entry) => !entry.ok).length;
|
|
17669
|
+
const elapsed = snapshot.thinkingMs >= 1e3 ? ` \xB7 Worked for ${formatSeconds(snapshot.thinkingMs)}` : "";
|
|
17670
|
+
const summary = `${expanded ? "\u25BE" : "\u25B8"} \u25CF ${snapshot.entries.length} ${snapshot.entries.length === 1 ? "tool call" : "tool calls"}${failures ? ` \xB7 ${failures} failed` : ""}${snapshot.finished ? elapsed : ""} \xB7 Ctrl+O details`;
|
|
17671
|
+
const rows = [{ text: summary, ok: failures || snapshot.failed ? false : snapshot.finished ? true : void 0 }];
|
|
17672
|
+
if (!expanded) {
|
|
17673
|
+
if (!snapshot.finished) for (let index = Math.max(0, snapshot.entries.length - 3); index < snapshot.entries.length; index += 1) {
|
|
17674
|
+
const entry = snapshot.entries[index];
|
|
17675
|
+
rows.push({ text: ` \u25CF ${entry.label}`, ok: entry.ok, entry: index });
|
|
17676
|
+
}
|
|
17677
|
+
return rows;
|
|
17678
|
+
}
|
|
17679
|
+
const last = Math.min(snapshot.entries.length, end);
|
|
17680
|
+
for (let index = Math.max(0, last - 5); index < last; index += 1) {
|
|
17681
|
+
const entry = snapshot.entries[index];
|
|
17682
|
+
const duration = entry.durationMs >= 1e3 ? ` \xB7 Worked for ${formatSeconds(entry.durationMs)}` : "";
|
|
17683
|
+
rows.push({ text: ` ${selected === index ? "\u25BE" : "\u25B8"} \u25CF ${entry.label}${duration}`, ok: entry.ok, entry: index });
|
|
17684
|
+
if (selected === index) {
|
|
17685
|
+
if (entry.request) rows.push({ text: ` ${entry.request}`, entry: index });
|
|
17686
|
+
if (entry.output) rows.push({ text: ` ${entry.output}`, ok: entry.ok, entry: index });
|
|
17687
|
+
}
|
|
17688
|
+
}
|
|
17689
|
+
if (snapshot.entries.length > 5) rows.push({ text: " Scroll to browse actions \xB7 /output for full saved results" });
|
|
17690
|
+
return rows;
|
|
17691
|
+
}
|
|
17692
|
+
|
|
17693
|
+
// src/ui/task-progress.ts
|
|
17694
|
+
function startTaskProgress(options) {
|
|
17695
|
+
let lastMessage = performance.now();
|
|
17696
|
+
let lastState = "";
|
|
17697
|
+
let stopped = false;
|
|
17698
|
+
const timer = setInterval(() => {
|
|
17699
|
+
if (stopped || options.holdWhile?.()) return;
|
|
17700
|
+
const state = options.snapshot();
|
|
17701
|
+
if (!state.activeTool && !state.waitingForModel) return;
|
|
17702
|
+
const fingerprint = `${state.completed}:${state.failed}:${state.activeTool ?? "model"}`;
|
|
17703
|
+
if (performance.now() - lastMessage < (fingerprint === lastState ? 6e4 : 3e4)) return;
|
|
17704
|
+
const completed = state.completed ? `I have completed ${state.completed} tool ${state.completed === 1 ? "call" : "calls"}.${state.failed ? ` Of those, ${state.failed} failed.` : ""} ` : "";
|
|
17705
|
+
const current = state.activeTool ? `I am running the ${state.activeTool} tool.` : "I am waiting for the model's next response.";
|
|
17706
|
+
options.write(`${completed}${current}`);
|
|
17707
|
+
lastMessage = performance.now();
|
|
17708
|
+
lastState = fingerprint;
|
|
17709
|
+
}, 5e3);
|
|
17710
|
+
timer.unref?.();
|
|
17711
|
+
return { markMessage: () => {
|
|
17712
|
+
lastMessage = performance.now();
|
|
17713
|
+
}, stop: () => {
|
|
17714
|
+
stopped = true;
|
|
17715
|
+
clearInterval(timer);
|
|
17716
|
+
} };
|
|
17717
|
+
}
|
|
17718
|
+
|
|
17393
17719
|
// src/ui/interactive-choice.ts
|
|
17394
17720
|
var MOUSE_ON = "\x1B[?1006h\x1B[?1000h";
|
|
17395
17721
|
var MOUSE_OFF = "\x1B[?1000l\x1B[?1006l";
|
|
17396
17722
|
function displayLabel(value, width2) {
|
|
17397
|
-
const
|
|
17723
|
+
const clean2 = value.replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\)|.)/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
17398
17724
|
let result = "";
|
|
17399
17725
|
let used = 0;
|
|
17400
|
-
for (const character of
|
|
17726
|
+
for (const character of clean2) {
|
|
17401
17727
|
const size = /[\p{Mark}\u200d\ufe0e\ufe0f]/u.test(character) ? 0 : character.codePointAt(0) < 128 ? 1 : 2;
|
|
17402
17728
|
if (used + size > width2 - 1) return `${result}\u2026`;
|
|
17403
17729
|
result += character;
|
|
@@ -17410,8 +17736,8 @@ function attachInteractiveChoices(readline, options, settings = {}) {
|
|
|
17410
17736
|
const output = settings.output ?? rl.output;
|
|
17411
17737
|
const prompt = settings.prompt ?? " \u203A ";
|
|
17412
17738
|
let resolveReady;
|
|
17413
|
-
const ready = new Promise((
|
|
17414
|
-
resolveReady =
|
|
17739
|
+
const ready = new Promise((resolve17) => {
|
|
17740
|
+
resolveReady = resolve17;
|
|
17415
17741
|
});
|
|
17416
17742
|
const original = rl._ttyWrite;
|
|
17417
17743
|
const priorPrompt = rl.getPrompt();
|
|
@@ -17617,6 +17943,15 @@ function attachInteractiveChoices(readline, options, settings = {}) {
|
|
|
17617
17943
|
}
|
|
17618
17944
|
|
|
17619
17945
|
// src/ui/slash-completion.ts
|
|
17946
|
+
function builtinSlashEntries(commands, help) {
|
|
17947
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
17948
|
+
for (const line of help.split("\n")) {
|
|
17949
|
+
const match = /^(\/[^\n]+?)\s{2,}(.+)$/.exec(line.trim());
|
|
17950
|
+
if (!match) continue;
|
|
17951
|
+
for (const command of match[1].matchAll(/\/([a-z-]+)/g)) descriptions.set(command[1], match[2]);
|
|
17952
|
+
}
|
|
17953
|
+
return commands.map((command) => ({ text: `/${command}`, label: `/${command}`, description: descriptions.get(command) }));
|
|
17954
|
+
}
|
|
17620
17955
|
function attachSlashCompletion(readline, loadEntries, render, initialEntries = []) {
|
|
17621
17956
|
const rl = readline;
|
|
17622
17957
|
const original = rl._ttyWrite;
|
|
@@ -17718,11 +18053,11 @@ function attachSlashCompletion(readline, loadEntries, render, initialEntries = [
|
|
|
17718
18053
|
|
|
17719
18054
|
// src/ui/terminal-layout.ts
|
|
17720
18055
|
import { Writable } from "stream";
|
|
17721
|
-
import { stripVTControlCharacters } from "util";
|
|
18056
|
+
import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
17722
18057
|
var CSI = "\x1B[";
|
|
17723
18058
|
var RESET_REGION = `${CSI}r`;
|
|
17724
18059
|
var MOUSE_OFF2 = `${CSI}?1000l${CSI}?1002l${CSI}?1003l${CSI}?1006l`;
|
|
17725
|
-
var BASE_FOOTER_ROWS =
|
|
18060
|
+
var BASE_FOOTER_ROWS = 4;
|
|
17726
18061
|
var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
17727
18062
|
var cells = (value) => [...segmenter.segment(value)].map(({ segment }) => {
|
|
17728
18063
|
const code = segment.codePointAt(0) ?? 0;
|
|
@@ -17731,7 +18066,7 @@ var cells = (value) => [...segmenter.segment(value)].map(({ segment }) => {
|
|
|
17731
18066
|
});
|
|
17732
18067
|
var width = (text) => cells(text).reduce((sum, cell) => sum + cell.width, 0);
|
|
17733
18068
|
function plain(value) {
|
|
17734
|
-
return
|
|
18069
|
+
return stripVTControlCharacters2(value).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
17735
18070
|
}
|
|
17736
18071
|
function fit(text, columns) {
|
|
17737
18072
|
if (columns <= 0) return "";
|
|
@@ -17745,7 +18080,6 @@ function fit(text, columns) {
|
|
|
17745
18080
|
return result;
|
|
17746
18081
|
}
|
|
17747
18082
|
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
18083
|
var ComposerOutput = class extends Writable {
|
|
17750
18084
|
constructor(layout) {
|
|
17751
18085
|
super();
|
|
@@ -17781,6 +18115,7 @@ var TerminalLayout = class {
|
|
|
17781
18115
|
details;
|
|
17782
18116
|
rl;
|
|
17783
18117
|
prompt = "\u203A ";
|
|
18118
|
+
inputFormatter;
|
|
17784
18119
|
transcriptRow = 1;
|
|
17785
18120
|
transcriptColumn = 1;
|
|
17786
18121
|
wrapPending = false;
|
|
@@ -17788,6 +18123,10 @@ var TerminalLayout = class {
|
|
|
17788
18123
|
lastFooterTop = 0;
|
|
17789
18124
|
suggestions = [];
|
|
17790
18125
|
selected = 0;
|
|
18126
|
+
activity;
|
|
18127
|
+
liveActivity;
|
|
18128
|
+
completedActivities = [];
|
|
18129
|
+
restoreActivityInput;
|
|
17791
18130
|
installed = false;
|
|
17792
18131
|
closed = false;
|
|
17793
18132
|
suspended = 0;
|
|
@@ -17796,6 +18135,7 @@ var TerminalLayout = class {
|
|
|
17796
18135
|
resize = () => {
|
|
17797
18136
|
if (this.closed || !this.enabled) return;
|
|
17798
18137
|
this.clearPreviousFooter();
|
|
18138
|
+
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1 && item.row + item.height - 1 <= this.contentRows);
|
|
17799
18139
|
this.transcriptRow = Math.min(this.transcriptRow, this.contentRows);
|
|
17800
18140
|
this.transcriptColumn = Math.min(this.transcriptColumn, this.columns);
|
|
17801
18141
|
this.rawWrite(`${CSI}1;${this.contentRows}r`);
|
|
@@ -17829,7 +18169,7 @@ var TerminalLayout = class {
|
|
|
17829
18169
|
return this.rows - BASE_FOOTER_ROWS - this.suggestionRows;
|
|
17830
18170
|
}
|
|
17831
18171
|
get inputRow() {
|
|
17832
|
-
return this.rows -
|
|
18172
|
+
return this.rows - 2;
|
|
17833
18173
|
}
|
|
17834
18174
|
install() {
|
|
17835
18175
|
if (this.installed || this.closed || !this.enabled) return this;
|
|
@@ -17851,6 +18191,155 @@ var TerminalLayout = class {
|
|
|
17851
18191
|
attachReadline(rl) {
|
|
17852
18192
|
this.rl = rl;
|
|
17853
18193
|
rl.setPrompt(this.prompt);
|
|
18194
|
+
const reader = rl;
|
|
18195
|
+
const original = reader._ttyWrite;
|
|
18196
|
+
let sequence = "";
|
|
18197
|
+
let sequenceTimer;
|
|
18198
|
+
const wrapper = (text, key) => {
|
|
18199
|
+
if ((this.activity || this.completedActivities.length) && !this.suspended && key?.ctrl && key.name === "o") {
|
|
18200
|
+
this.toggleActivity();
|
|
18201
|
+
return;
|
|
18202
|
+
}
|
|
18203
|
+
const value = key?.sequence ?? text ?? "";
|
|
18204
|
+
if (sequence || value.startsWith("\x1B[<")) {
|
|
18205
|
+
sequence += value;
|
|
18206
|
+
clearTimeout(sequenceTimer);
|
|
18207
|
+
const mouse = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(sequence);
|
|
18208
|
+
if (mouse) {
|
|
18209
|
+
sequence = "";
|
|
18210
|
+
if (!this.suspended) this.activityMouse(Number(mouse[1]), Number(mouse[3]), mouse[4] === "M");
|
|
18211
|
+
return;
|
|
18212
|
+
}
|
|
18213
|
+
if (/^\x1b\[<[0-9;]*$/.test(sequence) && sequence.length < 64) {
|
|
18214
|
+
sequenceTimer = setTimeout(() => {
|
|
18215
|
+
sequence = "";
|
|
18216
|
+
}, 250);
|
|
18217
|
+
return;
|
|
18218
|
+
}
|
|
18219
|
+
const pending = sequence;
|
|
18220
|
+
sequence = "";
|
|
18221
|
+
original.call(reader, pending, key);
|
|
18222
|
+
return;
|
|
18223
|
+
}
|
|
18224
|
+
original.call(reader, text, key);
|
|
18225
|
+
};
|
|
18226
|
+
reader._ttyWrite = wrapper;
|
|
18227
|
+
this.restoreActivityInput = () => {
|
|
18228
|
+
clearTimeout(sequenceTimer);
|
|
18229
|
+
if (reader._ttyWrite === wrapper) reader._ttyWrite = original;
|
|
18230
|
+
};
|
|
18231
|
+
this.paintFooter();
|
|
18232
|
+
}
|
|
18233
|
+
setActivity(snapshot) {
|
|
18234
|
+
this.activity = snapshot;
|
|
18235
|
+
if (!this.enabled || !this.installed || this.closed) return;
|
|
18236
|
+
if (!this.liveActivity && !snapshot.finished) {
|
|
18237
|
+
this.writeTranscript(`
|
|
18238
|
+
${this.colorActivityRow(activityRows(snapshot, false, -1, snapshot.entries.length)[0])}
|
|
18239
|
+
`);
|
|
18240
|
+
this.liveActivity = { snapshot, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1, end: snapshot.entries.length };
|
|
18241
|
+
this.completedActivities.push(this.liveActivity);
|
|
18242
|
+
}
|
|
18243
|
+
const item = this.liveActivity;
|
|
18244
|
+
if (!item) return;
|
|
18245
|
+
const previous = item.snapshot.entries.length;
|
|
18246
|
+
item.snapshot = snapshot;
|
|
18247
|
+
if (item.end >= previous) item.end = snapshot.entries.length;
|
|
18248
|
+
if (!snapshot.finished) {
|
|
18249
|
+
this.redrawCompleted(item);
|
|
18250
|
+
return;
|
|
18251
|
+
}
|
|
18252
|
+
if (item.row >= 2) {
|
|
18253
|
+
const removed = item.height + 1;
|
|
18254
|
+
this.rawWrite(`${cup(item.row - 1)}${CSI}${removed}M`);
|
|
18255
|
+
if (this.transcriptRow > item.row) this.transcriptRow -= removed;
|
|
18256
|
+
for (const other of this.completedActivities) if (other !== item && other.row > item.row) other.row -= removed;
|
|
18257
|
+
}
|
|
18258
|
+
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18259
|
+
this.liveActivity = void 0;
|
|
18260
|
+
this.writeTranscript(`${this.colorActivityRow(activityRows(snapshot, false, -1, snapshot.entries.length)[0])}
|
|
18261
|
+
`);
|
|
18262
|
+
this.completedActivities.push({ ...item, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1 });
|
|
18263
|
+
this.paintFooter();
|
|
18264
|
+
}
|
|
18265
|
+
setWorkingStatus(text) {
|
|
18266
|
+
const item = this.liveActivity;
|
|
18267
|
+
if (!item) return;
|
|
18268
|
+
if (item.row < 2 || item.row + item.height > this.contentRows) {
|
|
18269
|
+
if (item.row >= 1 && item.row <= this.contentRows) {
|
|
18270
|
+
this.rawWrite(`${cup(item.row)}${CSI}${item.height}M`);
|
|
18271
|
+
if (this.transcriptRow > item.row) this.transcriptRow -= item.height;
|
|
18272
|
+
for (const other of this.completedActivities) if (other !== item && other.row > item.row) other.row -= item.height;
|
|
18273
|
+
}
|
|
18274
|
+
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18275
|
+
this.writeTranscript(`
|
|
18276
|
+
${this.colorActivityRow(activityRows(item.snapshot, false, -1, item.end)[0])}
|
|
18277
|
+
`);
|
|
18278
|
+
item.row = this.transcriptRow - 1;
|
|
18279
|
+
item.height = 1;
|
|
18280
|
+
item.expanded = false;
|
|
18281
|
+
this.completedActivities.push(item);
|
|
18282
|
+
}
|
|
18283
|
+
this.rawWrite(`${cup(item.row - 1)}${CSI}0m${CSI}2K${fit(text, this.columns)}`);
|
|
18284
|
+
this.placeInputCursor();
|
|
18285
|
+
}
|
|
18286
|
+
colorActivityRow(row) {
|
|
18287
|
+
const text = fit(row.text, this.columns);
|
|
18288
|
+
const color = process.env.NO_COLOR || row.ok === void 0 ? "" : `${CSI}${row.ok ? 32 : 31}m`;
|
|
18289
|
+
const dot = text.indexOf("\u25CF");
|
|
18290
|
+
return dot >= 0 && color ? `${text.slice(0, dot)}${color}\u25CF${CSI}39m${text.slice(dot + 1)}` : text;
|
|
18291
|
+
}
|
|
18292
|
+
shiftCompleted(rows) {
|
|
18293
|
+
for (const item of this.completedActivities) item.row -= rows;
|
|
18294
|
+
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1);
|
|
18295
|
+
}
|
|
18296
|
+
redrawCompleted(item) {
|
|
18297
|
+
const rows = activityRows(item.snapshot, item.expanded, item.selected, item.end).slice(0, Math.max(1, this.contentRows - 2));
|
|
18298
|
+
const delta = rows.length - item.height;
|
|
18299
|
+
const scroll = Math.max(0, this.transcriptRow + delta - this.contentRows);
|
|
18300
|
+
if (scroll) {
|
|
18301
|
+
this.rawWrite(`${cup(this.contentRows)}${CSI}${scroll}S`);
|
|
18302
|
+
this.shiftCompleted(scroll);
|
|
18303
|
+
this.transcriptRow -= scroll;
|
|
18304
|
+
}
|
|
18305
|
+
if (item.row < 1) return;
|
|
18306
|
+
if (delta > 0) this.rawWrite(`${cup(item.row + item.height)}${CSI}${delta}L`);
|
|
18307
|
+
if (delta < 0) this.rawWrite(`${cup(item.row + rows.length)}${CSI}${-delta}M`);
|
|
18308
|
+
for (const other of this.completedActivities) if (other !== item && other.row > item.row) other.row += delta;
|
|
18309
|
+
if (this.transcriptRow > item.row) this.transcriptRow += delta;
|
|
18310
|
+
item.height = rows.length;
|
|
18311
|
+
rows.forEach((row, index) => this.rawWrite(`${cup(item.row + index)}${CSI}0m${CSI}2K${this.colorActivityRow(row)}`));
|
|
18312
|
+
this.paintFooter();
|
|
18313
|
+
}
|
|
18314
|
+
toggleActivity() {
|
|
18315
|
+
if (this.liveActivity && (this.liveActivity.row < 2 || this.liveActivity.row + this.liveActivity.height > this.contentRows)) this.setWorkingStatus("working\u2026");
|
|
18316
|
+
const item = this.liveActivity ?? this.completedActivities.at(-1);
|
|
18317
|
+
if (item) {
|
|
18318
|
+
item.expanded = !item.expanded;
|
|
18319
|
+
this.redrawCompleted(item);
|
|
18320
|
+
}
|
|
18321
|
+
}
|
|
18322
|
+
activityMouse(button, row, press) {
|
|
18323
|
+
if (!press) return;
|
|
18324
|
+
const completed = this.completedActivities.find((item) => row >= item.row && row < item.row + item.height);
|
|
18325
|
+
if (completed && (button === 64 || button === 65)) {
|
|
18326
|
+
if (!completed.expanded) return;
|
|
18327
|
+
completed.end = Math.max(Math.min(5, completed.snapshot.entries.length), Math.min(completed.snapshot.entries.length, completed.end + (button === 64 ? -1 : 1)));
|
|
18328
|
+
completed.selected = -1;
|
|
18329
|
+
this.redrawCompleted(completed);
|
|
18330
|
+
return;
|
|
18331
|
+
}
|
|
18332
|
+
if (completed && button === 0) {
|
|
18333
|
+
const rows = activityRows(completed.snapshot, completed.expanded, completed.selected, completed.end);
|
|
18334
|
+
const entry = rows[row - completed.row]?.entry;
|
|
18335
|
+
if (row === completed.row) completed.expanded = !completed.expanded;
|
|
18336
|
+
else if (entry !== void 0) completed.selected = completed.selected === entry ? -1 : entry;
|
|
18337
|
+
this.redrawCompleted(completed);
|
|
18338
|
+
return;
|
|
18339
|
+
}
|
|
18340
|
+
}
|
|
18341
|
+
setInputFormatter(formatter) {
|
|
18342
|
+
this.inputFormatter = formatter;
|
|
17854
18343
|
this.paintFooter();
|
|
17855
18344
|
}
|
|
17856
18345
|
setPrompt(prompt) {
|
|
@@ -17910,9 +18399,10 @@ var TerminalLayout = class {
|
|
|
17910
18399
|
for (let row = first; row <= last; row += 1) erased += `${cup(row)}${CSI}2K`;
|
|
17911
18400
|
return erased + saved;
|
|
17912
18401
|
});
|
|
17913
|
-
if (this.wrapPending && !/^[\r\n]/.test(
|
|
18402
|
+
if (this.wrapPending && !/^[\r\n]/.test(stripVTControlCharacters2(formatted))) {
|
|
17914
18403
|
this.rawWrite(`${cup(this.transcriptRow, this.columns)}\r
|
|
17915
18404
|
`);
|
|
18405
|
+
if (this.transcriptRow === this.contentRows) this.shiftCompleted(1);
|
|
17916
18406
|
this.transcriptRow = Math.min(this.contentRows, this.transcriptRow + 1);
|
|
17917
18407
|
this.transcriptColumn = 1;
|
|
17918
18408
|
this.wrapPending = false;
|
|
@@ -17977,6 +18467,7 @@ var TerminalLayout = class {
|
|
|
17977
18467
|
this.wrapPending = true;
|
|
17978
18468
|
}
|
|
17979
18469
|
}
|
|
18470
|
+
if (this.transcriptRow > this.contentRows) this.shiftCompleted(this.transcriptRow - this.contentRows);
|
|
17980
18471
|
this.transcriptRow = Math.min(this.contentRows, this.transcriptRow);
|
|
17981
18472
|
}
|
|
17982
18473
|
}
|
|
@@ -17987,8 +18478,10 @@ var TerminalLayout = class {
|
|
|
17987
18478
|
composer() {
|
|
17988
18479
|
const available = this.columns - 4;
|
|
17989
18480
|
const prompt = fit(this.rl?.getPrompt() ?? this.prompt, Math.min(24, Math.max(2, Math.floor(available / 3))));
|
|
17990
|
-
const
|
|
17991
|
-
const
|
|
18481
|
+
const raw = this.rl?.line ?? "";
|
|
18482
|
+
const formatted = this.inputFormatter?.(raw, this.rl?.cursor ?? 0) ?? { text: raw, cursor: this.rl?.cursor ?? 0 };
|
|
18483
|
+
const input = plain(formatted.text);
|
|
18484
|
+
const cursor = width(plain(formatted.text.slice(0, formatted.cursor)));
|
|
17992
18485
|
const capacity = Math.max(1, available - width(prompt));
|
|
17993
18486
|
const offset = Math.max(0, cursor - capacity + 1);
|
|
17994
18487
|
let visible = "";
|
|
@@ -18021,29 +18514,45 @@ var TerminalLayout = class {
|
|
|
18021
18514
|
const inner = this.columns - 2;
|
|
18022
18515
|
const border = "\u2500".repeat(inner);
|
|
18023
18516
|
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)}`;
|
|
18517
|
+
const model = /^cosmic[- ]1$/i.test(this.details.model) ? "Cosmic 1" : this.details.model;
|
|
18518
|
+
const metadata = fit(model, this.columns);
|
|
18028
18519
|
let frame = "";
|
|
18520
|
+
const activity = [];
|
|
18521
|
+
if ((activity.length || this.completedActivities.length) && !this.suspended) frame += `${CSI}?1006h${CSI}?1000h`;
|
|
18522
|
+
activity.forEach((row, index) => {
|
|
18523
|
+
const rendered = this.colorActivityRow(row);
|
|
18524
|
+
frame += `${cup(this.contentRows + 1 + index)}${CSI}0m${CSI}2K${rendered}`;
|
|
18525
|
+
});
|
|
18029
18526
|
const suggestions = this.suggestionRows;
|
|
18030
18527
|
const first = Math.max(0, Math.min(this.selected - Math.floor(suggestions / 2), this.suggestions.length - suggestions));
|
|
18528
|
+
const commandWidth = Math.min(
|
|
18529
|
+
Math.max(18, ...this.suggestions.map((row) => width(plain(row).split(/ {2,}/)[0] ?? ""))),
|
|
18530
|
+
Math.max(8, Math.floor(this.columns / 3))
|
|
18531
|
+
);
|
|
18031
18532
|
for (let i = 0; i < suggestions; i += 1) {
|
|
18032
18533
|
const index = first + i;
|
|
18033
18534
|
const selected = index === this.selected;
|
|
18034
|
-
|
|
18535
|
+
const entry = plain(this.suggestions[index] ?? "");
|
|
18536
|
+
const match = /^(.+?) {2,}(.+)$/.exec(entry);
|
|
18537
|
+
const command = fit(match?.[1] ?? entry, commandWidth);
|
|
18538
|
+
const description = match?.[2] ?? "";
|
|
18539
|
+
const row = fit(`${selected ? "\u203A" : " "} ${command}${description ? `${" ".repeat(Math.max(0, commandWidth - width(command)) + 6)}${description}` : ""}`, this.columns);
|
|
18540
|
+
const style = process.env.NO_COLOR ? "" : selected ? `${CSI}1m${CSI}39m${CSI}49m` : `${CSI}22m${CSI}39m${CSI}49m`;
|
|
18541
|
+
frame += `${cup(this.contentRows + activity.length + 1 + i)}${CSI}0m${CSI}2K${style}${row}${" ".repeat(this.columns - width(row))}${CSI}0m`;
|
|
18035
18542
|
}
|
|
18036
18543
|
const inputText = fit(composer.text, this.columns - 4);
|
|
18037
18544
|
const paddedInput = inputText + " ".repeat(Math.max(0, this.columns - 4 - width(inputText)));
|
|
18038
|
-
const
|
|
18545
|
+
const dimBorder = (value) => process.env.NO_COLOR ? value : `${CSI}90m${value}${CSI}39m`;
|
|
18546
|
+
const lines = [dimBorder(`\u250C${border}\u2510`), `${dimBorder("\u2502")} ${paddedInput} ${dimBorder("\u2502")}`, dimBorder(`\u2514${border}\u2518`), metadata];
|
|
18039
18547
|
lines.forEach((line, index) => {
|
|
18040
|
-
frame += `${cup(this.rows -
|
|
18548
|
+
frame += `${cup(this.rows - 3 + index)}${CSI}2K${line}`;
|
|
18041
18549
|
});
|
|
18042
18550
|
this.rawWrite(`${frame}${cup(this.inputRow, composer.column)}${CSI}?25h`);
|
|
18043
18551
|
}
|
|
18044
18552
|
close() {
|
|
18045
18553
|
if (this.closed) return;
|
|
18046
18554
|
this.closed = true;
|
|
18555
|
+
this.restoreActivityInput?.();
|
|
18047
18556
|
if (!this.installed) return;
|
|
18048
18557
|
this.output.write = this.originalWrite;
|
|
18049
18558
|
if (this.errorOutput && this.errorOutput !== this.output && this.originalErrorWrite) this.errorOutput.write = this.originalErrorWrite;
|
|
@@ -18068,6 +18577,26 @@ function createTerminalLayout(options) {
|
|
|
18068
18577
|
|
|
18069
18578
|
// src/ui/welcome.ts
|
|
18070
18579
|
import chalk10, { Chalk } from "chalk";
|
|
18580
|
+
import { homedir as homedir3 } from "os";
|
|
18581
|
+
import { sep } from "path";
|
|
18582
|
+
|
|
18583
|
+
// src/ui/provider-brand.ts
|
|
18584
|
+
import { stripVTControlCharacters as stripVTControlCharacters3 } from "util";
|
|
18585
|
+
var GOLD = "\x1B[38;2;202;168;103m";
|
|
18586
|
+
var DEFAULT_FOREGROUND = "\x1B[39m";
|
|
18587
|
+
function safeDisplayText(value) {
|
|
18588
|
+
return stripVTControlCharacters3(value).replace(/[\x00-\x1f\x7f]/g, " ");
|
|
18589
|
+
}
|
|
18590
|
+
function providerDisplayName(provider) {
|
|
18591
|
+
const name = safeDisplayText(provider);
|
|
18592
|
+
return name.toLowerCase() === "ultrac" ? "spinalfluid" : name;
|
|
18593
|
+
}
|
|
18594
|
+
function paintProviderSpan(line, offset, provider, color) {
|
|
18595
|
+
if (!color || provider !== "spinalfluid" || offset < 0 || line.slice(offset, offset + provider.length) !== provider) return line;
|
|
18596
|
+
return `${line.slice(0, offset)}${GOLD}${provider}${DEFAULT_FOREGROUND}${line.slice(offset + provider.length)}`;
|
|
18597
|
+
}
|
|
18598
|
+
|
|
18599
|
+
// src/ui/welcome.ts
|
|
18071
18600
|
var LOGO = [
|
|
18072
18601
|
"\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
18602
|
"\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 +18608,7 @@ var LOGO = [
|
|
|
18079
18608
|
function renderWelcome(info, options = {}) {
|
|
18080
18609
|
const columns = normalizeColumns(options.columns);
|
|
18081
18610
|
const paint = new Chalk({ level: options.color === false ? 0 : chalk10.level });
|
|
18082
|
-
const
|
|
18083
|
-
const innerWidth = cardWidth - 4;
|
|
18611
|
+
const innerWidth = columns - 2;
|
|
18084
18612
|
const wide = columns >= 64;
|
|
18085
18613
|
const lines = [];
|
|
18086
18614
|
if (wide) {
|
|
@@ -18091,16 +18619,14 @@ function renderWelcome(info, options = {}) {
|
|
|
18091
18619
|
} else {
|
|
18092
18620
|
lines.push("", ` ${paint.bold.white("ULTRAC")} ${paint.gray(`v${info.version}`)}`);
|
|
18093
18621
|
}
|
|
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`));
|
|
18622
|
+
const provider = providerDisplayName(info.providerName);
|
|
18623
|
+
const prefix = `${safeDisplayText(info.model)} \xB7 `;
|
|
18624
|
+
const account = `${provider}${info.planName ? ` ${safeDisplayText(info.planName)}` : ""}`;
|
|
18625
|
+
const modelLine = ` ${truncate3(`${prefix}${account}`, innerWidth)}`;
|
|
18626
|
+
lines.push("", paintProviderSpan(modelLine, 2 + prefix.length, provider, paint.level > 0));
|
|
18627
|
+
const home = homedir3();
|
|
18628
|
+
const workspace = info.workspace === home ? "~" : info.workspace.startsWith(`${home}${sep}`) ? `~${info.workspace.slice(home.length)}` : info.workspace;
|
|
18629
|
+
lines.push(paint.gray(` ${shortenMiddle(safeDisplayText(workspace), innerWidth)}`), "");
|
|
18104
18630
|
lines.push(
|
|
18105
18631
|
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
18632
|
);
|
|
@@ -18118,10 +18644,6 @@ ${paint.gray("\u2502")}`,
|
|
|
18118
18644
|
prompt: paint.bold.white("\u2570\u2500\u276F ")
|
|
18119
18645
|
};
|
|
18120
18646
|
}
|
|
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
18647
|
function normalizeColumns(columns) {
|
|
18126
18648
|
return Number.isFinite(columns) ? Math.max(16, Math.floor(columns)) : 80;
|
|
18127
18649
|
}
|
|
@@ -18137,11 +18659,18 @@ function shortenMiddle(value, width2) {
|
|
|
18137
18659
|
return `${value.slice(0, side)}\u2026${value.slice(value.length - (width2 - side - 1))}`;
|
|
18138
18660
|
}
|
|
18139
18661
|
|
|
18662
|
+
// src/ui/update-prompt.ts
|
|
18663
|
+
import { constants as constants7 } from "fs";
|
|
18664
|
+
import { lstat as lstat4, mkdir as mkdir15, open as open10, rename as rename4, unlink as unlink5 } from "fs/promises";
|
|
18665
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
18666
|
+
import { homedir as homedir4 } from "os";
|
|
18667
|
+
import { join as join15, parse, resolve as resolve13 } from "path";
|
|
18668
|
+
|
|
18140
18669
|
// src/update/check.ts
|
|
18141
18670
|
import { spawn as spawn4 } from "child_process";
|
|
18142
18671
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
18143
18672
|
import { readFile as readFile13, rename as rename3, unlink as unlink4, writeFile as writeFile12 } from "fs/promises";
|
|
18144
|
-
import { join as join14 } from "path";
|
|
18673
|
+
import { isAbsolute as isAbsolute5, join as join14, relative as relative2, resolve as resolve12 } from "path";
|
|
18145
18674
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
18146
18675
|
var QUERY_TIMEOUT_MS = 5e3;
|
|
18147
18676
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -18174,17 +18703,67 @@ async function queryLatestVersion(packageName) {
|
|
|
18174
18703
|
return void 0;
|
|
18175
18704
|
}
|
|
18176
18705
|
}
|
|
18177
|
-
async function installLatestVersion(packageName) {
|
|
18706
|
+
async function installLatestVersion(packageName, version = "latest") {
|
|
18707
|
+
if (version !== "latest" && !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
18708
|
+
throw new Error("Invalid npm release version.");
|
|
18709
|
+
}
|
|
18178
18710
|
const result = await runNpm(
|
|
18179
|
-
["install", "--global", `${packageName}
|
|
18711
|
+
["install", "--global", `${packageName}@${version}`, "--no-audit", "--no-fund"],
|
|
18180
18712
|
{ timeoutMs: INSTALL_TIMEOUT_MS, capture: false }
|
|
18181
18713
|
);
|
|
18182
18714
|
if (result.code !== 0) {
|
|
18183
18715
|
throw new Error(
|
|
18184
|
-
`npm could not install ${packageName}
|
|
18716
|
+
`npm could not install ${packageName}@${version}. Confirm npm can reach the registry and install global packages.`
|
|
18185
18717
|
);
|
|
18186
18718
|
}
|
|
18187
18719
|
}
|
|
18720
|
+
async function installVerifiedVersion(packageName, version) {
|
|
18721
|
+
await installLatestVersion(packageName, version);
|
|
18722
|
+
const result = await runNpm(["list", "--global", packageName, "--depth=0", "--json"], {
|
|
18723
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18724
|
+
capture: true,
|
|
18725
|
+
maxOutputBytes: 4096
|
|
18726
|
+
});
|
|
18727
|
+
let installedVersion;
|
|
18728
|
+
try {
|
|
18729
|
+
const parsed = JSON.parse(result.stdout);
|
|
18730
|
+
if (typeof parsed === "object" && parsed !== null && "dependencies" in parsed && typeof parsed.dependencies === "object" && parsed.dependencies !== null) {
|
|
18731
|
+
const dependency = parsed.dependencies[packageName];
|
|
18732
|
+
if (typeof dependency === "object" && dependency !== null && "version" in dependency) {
|
|
18733
|
+
installedVersion = dependency.version;
|
|
18734
|
+
}
|
|
18735
|
+
}
|
|
18736
|
+
} catch {
|
|
18737
|
+
}
|
|
18738
|
+
if (result.code !== 0 || installedVersion !== version) {
|
|
18739
|
+
throw new Error("The update could not be verified. Run ultrac update and restart Ultrac.");
|
|
18740
|
+
}
|
|
18741
|
+
try {
|
|
18742
|
+
const globalRoot = await runNpm(["root", "--global"], {
|
|
18743
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18744
|
+
capture: true,
|
|
18745
|
+
maxOutputBytes: 4096
|
|
18746
|
+
});
|
|
18747
|
+
const root = globalRoot.stdout.trim();
|
|
18748
|
+
if (globalRoot.code !== 0 || !isAbsolute5(root) || root.includes("\n")) throw new Error("Invalid npm root");
|
|
18749
|
+
const packageRoot = join14(root, packageName);
|
|
18750
|
+
const manifest = JSON.parse(await readFile13(join14(packageRoot, "package.json"), "utf8"));
|
|
18751
|
+
if (typeof manifest !== "object" || manifest === null || !("version" in manifest) || manifest.version !== version || !("bin" in manifest)) throw new Error("Invalid package");
|
|
18752
|
+
const bin = manifest.bin;
|
|
18753
|
+
const entry = typeof bin === "string" ? bin : typeof bin === "object" && bin !== null && "ultrac" in bin ? bin.ultrac : void 0;
|
|
18754
|
+
if (typeof entry !== "string") throw new Error("Missing executable");
|
|
18755
|
+
const executable = resolve12(packageRoot, entry);
|
|
18756
|
+
const withinPackage = relative2(packageRoot, executable);
|
|
18757
|
+
if (withinPackage.startsWith("..") || isAbsolute5(withinPackage)) throw new Error("Invalid executable");
|
|
18758
|
+
const probe = await runExecutable(process.execPath, [executable, "--version"], {
|
|
18759
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
18760
|
+
capture: true
|
|
18761
|
+
});
|
|
18762
|
+
if (probe.code !== 0 || probe.stdout.trim() !== version) throw new Error("Invalid executable version");
|
|
18763
|
+
} catch {
|
|
18764
|
+
throw new Error("The installed Ultrac executable could not be verified. Restart and run ultrac --version.");
|
|
18765
|
+
}
|
|
18766
|
+
}
|
|
18188
18767
|
function isNewerVersion(candidate, current) {
|
|
18189
18768
|
const left = parseVersion(candidate);
|
|
18190
18769
|
const right = parseVersion(current);
|
|
@@ -18237,12 +18816,15 @@ async function writeCache(path, cache) {
|
|
|
18237
18816
|
}
|
|
18238
18817
|
}
|
|
18239
18818
|
async function runNpm(args, options) {
|
|
18819
|
+
return runExecutable(process.platform === "win32" ? "npm.cmd" : "npm", args, options);
|
|
18820
|
+
}
|
|
18821
|
+
async function runExecutable(executable, args, options) {
|
|
18240
18822
|
const env2 = { ...process.env };
|
|
18241
18823
|
for (const key of Object.keys(env2)) {
|
|
18242
18824
|
if (key.startsWith("ULTRAC_")) delete env2[key];
|
|
18243
18825
|
}
|
|
18244
|
-
return new Promise((
|
|
18245
|
-
const child = spawn4(
|
|
18826
|
+
return new Promise((resolve17) => {
|
|
18827
|
+
const child = spawn4(executable, args, {
|
|
18246
18828
|
env: env2,
|
|
18247
18829
|
stdio: options.capture ? ["ignore", "pipe", "ignore"] : "inherit",
|
|
18248
18830
|
windowsHide: true
|
|
@@ -18251,21 +18833,90 @@ async function runNpm(args, options) {
|
|
|
18251
18833
|
if (options.capture) {
|
|
18252
18834
|
child.stdout?.setEncoding("utf8");
|
|
18253
18835
|
child.stdout?.on("data", (chunk) => {
|
|
18254
|
-
|
|
18836
|
+
const limit = options.maxOutputBytes ?? 256;
|
|
18837
|
+
if (stdout6.length <= limit) stdout6 += chunk.slice(0, limit + 1 - stdout6.length);
|
|
18255
18838
|
});
|
|
18256
18839
|
}
|
|
18257
18840
|
const timer = setTimeout(() => child.kill(), options.timeoutMs);
|
|
18258
18841
|
child.once("error", () => {
|
|
18259
18842
|
clearTimeout(timer);
|
|
18260
|
-
|
|
18843
|
+
resolve17({ code: 1, stdout: "" });
|
|
18261
18844
|
});
|
|
18262
18845
|
child.once("close", (code) => {
|
|
18263
18846
|
clearTimeout(timer);
|
|
18264
|
-
|
|
18847
|
+
resolve17({ code: code ?? 1, stdout: stdout6 });
|
|
18265
18848
|
});
|
|
18266
18849
|
});
|
|
18267
18850
|
}
|
|
18268
18851
|
|
|
18852
|
+
// src/ui/update-prompt.ts
|
|
18853
|
+
var UPDATE_CHOICES = ["Update now", "Skip", "Skip until next version"];
|
|
18854
|
+
async function offerUpdatePrompt(status, options) {
|
|
18855
|
+
const version = status.latestVersion;
|
|
18856
|
+
if (!options.interactive || options.signal?.aborted || !status.updateAvailable || !version || !validVersion(version) || !validVersion(status.currentVersion) || !isNewerVersion(version, status.currentVersion) || await skippedVersion() === version) return "not_offered";
|
|
18857
|
+
const answer = await options.ask(
|
|
18858
|
+
`Ultrac ${version} is available. You are running ${status.currentVersion}.`,
|
|
18859
|
+
UPDATE_CHOICES,
|
|
18860
|
+
options.signal
|
|
18861
|
+
);
|
|
18862
|
+
if (options.signal?.aborted) return "skip";
|
|
18863
|
+
if (answer === UPDATE_CHOICES[0]) return "update_now";
|
|
18864
|
+
if (answer === UPDATE_CHOICES[2]) {
|
|
18865
|
+
await rememberSkippedVersion(version);
|
|
18866
|
+
return "skip_version";
|
|
18867
|
+
}
|
|
18868
|
+
return "skip";
|
|
18869
|
+
}
|
|
18870
|
+
function validVersion(version) {
|
|
18871
|
+
return version.length <= 128 && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
|
|
18872
|
+
}
|
|
18873
|
+
async function statePath(create) {
|
|
18874
|
+
const home = resolve13(ultracHomeDir());
|
|
18875
|
+
if (home === parse(home).root || home === resolve13(homedir4())) {
|
|
18876
|
+
throw new Error("Ultrac update preferences require a dedicated ULTRAC_HOME directory.");
|
|
18877
|
+
}
|
|
18878
|
+
if (create) await mkdir15(home, { recursive: true, mode: 448 });
|
|
18879
|
+
const info = await lstat4(home);
|
|
18880
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
18881
|
+
throw new Error("Ultrac update preferences require a real home directory.");
|
|
18882
|
+
}
|
|
18883
|
+
return join15(home, "update-preferences.json");
|
|
18884
|
+
}
|
|
18885
|
+
async function skippedVersion() {
|
|
18886
|
+
try {
|
|
18887
|
+
const path = await statePath(false);
|
|
18888
|
+
const file = await open10(path, constants7.O_RDONLY | constants7.O_NOFOLLOW);
|
|
18889
|
+
try {
|
|
18890
|
+
const info = await file.stat();
|
|
18891
|
+
if (!info.isFile() || info.size > 1024) return void 0;
|
|
18892
|
+
const parsed = JSON.parse(await file.readFile("utf8"));
|
|
18893
|
+
if (typeof parsed !== "object" || parsed === null || !("skippedVersion" in parsed)) return void 0;
|
|
18894
|
+
return typeof parsed.skippedVersion === "string" && validVersion(parsed.skippedVersion) ? parsed.skippedVersion : void 0;
|
|
18895
|
+
} finally {
|
|
18896
|
+
await file.close();
|
|
18897
|
+
}
|
|
18898
|
+
} catch {
|
|
18899
|
+
return void 0;
|
|
18900
|
+
}
|
|
18901
|
+
}
|
|
18902
|
+
async function rememberSkippedVersion(version) {
|
|
18903
|
+
const path = await statePath(true);
|
|
18904
|
+
const temporary = `${path}.${randomBytes5(8).toString("hex")}.tmp`;
|
|
18905
|
+
try {
|
|
18906
|
+
const file = await open10(temporary, constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL, 384);
|
|
18907
|
+
try {
|
|
18908
|
+
await file.writeFile(`${JSON.stringify({ skippedVersion: version })}
|
|
18909
|
+
`, "utf8");
|
|
18910
|
+
await file.sync();
|
|
18911
|
+
} finally {
|
|
18912
|
+
await file.close();
|
|
18913
|
+
}
|
|
18914
|
+
await rename4(temporary, path);
|
|
18915
|
+
} finally {
|
|
18916
|
+
await unlink5(temporary).catch(() => void 0);
|
|
18917
|
+
}
|
|
18918
|
+
}
|
|
18919
|
+
|
|
18269
18920
|
// src/commands/repl.ts
|
|
18270
18921
|
var HELP = `Commands:
|
|
18271
18922
|
/help Show this help
|
|
@@ -18296,7 +18947,7 @@ var HELP = `Commands:
|
|
|
18296
18947
|
/whoami Show sign-in state
|
|
18297
18948
|
/tools List available tools
|
|
18298
18949
|
/logs Show the private diagnostic log path
|
|
18299
|
-
/ghost [on|off|status]
|
|
18950
|
+
/ghost [on|off|status] Quiet activity output; diagnostics stay enabled
|
|
18300
18951
|
/output [number|list] Inspect saved tool output (latest by default)
|
|
18301
18952
|
/memory List and inspect stored memories
|
|
18302
18953
|
/skill <name> [args] Run an installed skill
|
|
@@ -18312,11 +18963,12 @@ Custom commands come from skills that declare a 'command:' key in their
|
|
|
18312
18963
|
frontmatter; built-ins always win a name clash. Type anything else to talk to Ultrac.`;
|
|
18313
18964
|
var EXIT_REPL_NOT_STARTED = 1;
|
|
18314
18965
|
function registerReplCommand(program2) {
|
|
18315
|
-
program2.command("interactive", { isDefault: true }).alias("repl").description("Start an interactive ultrac session").argument("[task...]", "Optional initial prompt to send on start").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps per turn").option("--allow-write", "Allow file writes without prompting", false).option("--allow-shell", "Grant broad, unsandboxed shell access without prompting", false).option("--allow-network", "Allow network access without prompting", false).option("--no-native-tools", "Use the JSON tool protocol instead of native tool calling").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").action(async (taskParts, options, command) => {
|
|
18966
|
+
program2.command("interactive", { isDefault: true }).alias("repl").description("Start an interactive ultrac session").argument("[task...]", "Optional initial prompt to send on start").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps per turn").option("--permissions <mode>", "Tool permission preset: readonly, workspace, or full (no tool approval prompts)").option("--allow-write", "Allow file writes without prompting", false).option("--allow-shell", "Grant broad, unsandboxed shell access without prompting", false).option("--allow-network", "Allow network access without prompting", false).option("--no-native-tools", "Use the JSON tool protocol instead of native tool calling").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").action(async (taskParts, options, command) => {
|
|
18316
18967
|
const task = (taskParts ?? []).join(" ").trim();
|
|
18317
18968
|
const invokedByName = namesReplCommand(command, command.parent?.args[0]);
|
|
18318
18969
|
const mistyped = invokedByName ? void 0 : mistypedCommand(task, knownCommandNames(program2));
|
|
18319
18970
|
if (mistyped) throw new Error(mistyped);
|
|
18971
|
+
if (options.permissions && !["readonly", "workspace", "full"].includes(options.permissions)) throw new Error("--permissions must be readonly, workspace, or full.");
|
|
18320
18972
|
await runRepl(options, task);
|
|
18321
18973
|
});
|
|
18322
18974
|
}
|
|
@@ -18424,7 +19076,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18424
19076
|
diagnosticEvent("provider.retry", event);
|
|
18425
19077
|
console.log(
|
|
18426
19078
|
chalk11.yellow(
|
|
18427
|
-
`
|
|
19079
|
+
` ${event.classification.reason} Retrying ${event.target.model} in ${(event.delayMs / 1e3).toFixed(1)} seconds (retry ${event.attempt}).`
|
|
18428
19080
|
)
|
|
18429
19081
|
);
|
|
18430
19082
|
}
|
|
@@ -18435,9 +19087,9 @@ async function runRepl(options, initialTask = "") {
|
|
|
18435
19087
|
return;
|
|
18436
19088
|
}
|
|
18437
19089
|
const basePermissions = {
|
|
18438
|
-
write: options.allowWrite || config.permissions.write,
|
|
18439
|
-
shell: options.allowShell || config.permissions.shell,
|
|
18440
|
-
network: options.allowNetwork || config.permissions.network
|
|
19090
|
+
write: options.permissions ? options.permissions !== "readonly" : options.allowWrite || config.permissions.write,
|
|
19091
|
+
shell: options.permissions ? options.permissions === "full" : options.allowShell || config.permissions.shell,
|
|
19092
|
+
network: options.permissions ? options.permissions === "full" : options.allowNetwork || config.permissions.network
|
|
18441
19093
|
};
|
|
18442
19094
|
const registry = createDefaultToolRegistry();
|
|
18443
19095
|
const mcpRuntime = await connectWorkspaceMcpServers(config.mcpServers, { workspace, registry });
|
|
@@ -18454,7 +19106,11 @@ async function runRepl(options, initialTask = "") {
|
|
|
18454
19106
|
...toolSelection ? { tools: toolSelection } : {}
|
|
18455
19107
|
};
|
|
18456
19108
|
setSubagentRuntime({ provider, model: init.model, mode: init.mode, registry });
|
|
18457
|
-
|
|
19109
|
+
const created = useNativeTools ? new NativeSession(opts) : new InteractiveSession(opts);
|
|
19110
|
+
if (options.permissions) for (const permission of ["write", "shell", "network"]) {
|
|
19111
|
+
created.setPermission(permission, init.permissions[permission]);
|
|
19112
|
+
}
|
|
19113
|
+
return created;
|
|
18458
19114
|
};
|
|
18459
19115
|
let session = createSession({
|
|
18460
19116
|
model,
|
|
@@ -18467,6 +19123,8 @@ async function runRepl(options, initialTask = "") {
|
|
|
18467
19123
|
bindSignals: false,
|
|
18468
19124
|
details: { provider: provider.name, model: session.model, mode: session.mode, status: "Idle" }
|
|
18469
19125
|
}) : void 0;
|
|
19126
|
+
setActivitySink(layout?.enabled ? (snapshot) => layout.setActivity(snapshot) : void 0);
|
|
19127
|
+
setWorkingSink(layout?.enabled ? (text) => layout.setWorkingStatus(text) : void 0);
|
|
18470
19128
|
const refreshDetails = () => {
|
|
18471
19129
|
if (!layout?.enabled) return;
|
|
18472
19130
|
const steps = session.toSession().steps;
|
|
@@ -18504,7 +19162,8 @@ async function runRepl(options, initialTask = "") {
|
|
|
18504
19162
|
workspace,
|
|
18505
19163
|
useNativeTools ? "native tools" : "json protocol",
|
|
18506
19164
|
describeBudget(mode, toolSelection, registry),
|
|
18507
|
-
diagnosticPath2
|
|
19165
|
+
diagnosticPath2,
|
|
19166
|
+
provider.name.toLowerCase() === "ultrac" ? await readAccountPlan(await resolveUltracCredentials()) : void 0
|
|
18508
19167
|
);
|
|
18509
19168
|
if (mcpRuntime && mcpRuntime.statuses.length > 0) {
|
|
18510
19169
|
console.log(chalk11.bold("\nMCP servers"));
|
|
@@ -18517,15 +19176,14 @@ async function runRepl(options, initialTask = "") {
|
|
|
18517
19176
|
throw new Error(`Could not start the session: ${errorMessage2(error)}`, { cause: error });
|
|
18518
19177
|
}
|
|
18519
19178
|
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."));
|
|
19179
|
+
if (!stdin5.isTTY && update?.updateAvailable && update.latestVersion) {
|
|
19180
|
+
console.log(chalk11.yellow(`Update available: ${update.currentVersion} \u2192 ${update.latestVersion}. Run ultrac update.`));
|
|
18524
19181
|
}
|
|
18525
19182
|
const rl = createInterface3({ input: stdin5, output: layout?.enabled ? layout.outputProxy : stdout5 });
|
|
18526
19183
|
layout?.attachReadline(rl);
|
|
19184
|
+
layout?.setInputFormatter(formatImageInput);
|
|
18527
19185
|
refreshDetails();
|
|
18528
|
-
const builtinEntries = BUILTIN_SLASH_COMMANDS
|
|
19186
|
+
const builtinEntries = builtinSlashEntries(BUILTIN_SLASH_COMMANDS, HELP);
|
|
18529
19187
|
const slashCompletion = layout?.enabled ? attachSlashCompletion(rl, async () => {
|
|
18530
19188
|
const [commands, skills] = await Promise.all([listSkillCommands(workspace), listSkills(workspace)]);
|
|
18531
19189
|
const declared = new Set(commands.commands.map((command) => command.skillName));
|
|
@@ -18550,7 +19208,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18550
19208
|
}
|
|
18551
19209
|
};
|
|
18552
19210
|
const drainQueuedMessages = () => reader.drainQueuedMessages();
|
|
18553
|
-
let autonomousRounds =
|
|
19211
|
+
let autonomousRounds = 5;
|
|
18554
19212
|
setAskUserRuntime(async (text, options2, signal) => {
|
|
18555
19213
|
console.log(chalk11.white(`
|
|
18556
19214
|
? ${text}`));
|
|
@@ -18605,29 +19263,35 @@ async function runRepl(options, initialTask = "") {
|
|
|
18605
19263
|
rl.close();
|
|
18606
19264
|
};
|
|
18607
19265
|
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
19266
|
const withInterrupt = async (run) => {
|
|
18615
19267
|
turnAbort = new AbortController();
|
|
18616
|
-
|
|
18617
|
-
layout?.updateDetails({ status: "Running 00:00" });
|
|
19268
|
+
layout?.updateDetails({ status: "Running" });
|
|
18618
19269
|
try {
|
|
18619
19270
|
return await run(turnAbort.signal);
|
|
18620
19271
|
} finally {
|
|
18621
19272
|
turnAbort = void 0;
|
|
18622
|
-
turnStartedAt = void 0;
|
|
18623
19273
|
refreshDetails();
|
|
18624
19274
|
layout?.updateDetails({ status: "Idle" });
|
|
18625
19275
|
}
|
|
18626
19276
|
};
|
|
18627
19277
|
try {
|
|
19278
|
+
if (update) {
|
|
19279
|
+
const decision = await withInterrupt((signal) => offerUpdatePrompt(update, {
|
|
19280
|
+
interactive: Boolean(stdin5.isTTY && stdout5.isTTY),
|
|
19281
|
+
signal,
|
|
19282
|
+
ask: (text, choices, askSignal) => getAskUserRuntime()(text, [...choices], askSignal)
|
|
19283
|
+
}));
|
|
19284
|
+
if (decision === "update_now") {
|
|
19285
|
+
layout?.close();
|
|
19286
|
+
rl.close();
|
|
19287
|
+
await installVerifiedVersion(packageInfo.name, update.latestVersion);
|
|
19288
|
+
console.log(`Updated to ${update.latestVersion}. Run ultrac again to use the new version.`);
|
|
19289
|
+
return;
|
|
19290
|
+
}
|
|
19291
|
+
}
|
|
18628
19292
|
if (initialTask) {
|
|
18629
19293
|
console.log(chalk11.white(`
|
|
18630
|
-
\u203A ${initialTask}`));
|
|
19294
|
+
\u203A ${formatImageInput(initialTask).text}`));
|
|
18631
19295
|
await withInterrupt((signal) => runAndCheckpointTurn(session, initialTask, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds));
|
|
18632
19296
|
}
|
|
18633
19297
|
while (running) {
|
|
@@ -18642,10 +19306,10 @@ async function runRepl(options, initialTask = "") {
|
|
|
18642
19306
|
}
|
|
18643
19307
|
const line = await reader.next();
|
|
18644
19308
|
if (line === null) break;
|
|
18645
|
-
if (layout?.enabled && line.trim()) console.log(`\u203A ${line.replace(/[\x00-\x1f\x7f]/g, " ")}`);
|
|
19309
|
+
if (layout?.enabled && line.trim()) console.log(`\u203A ${formatImageInput(line).text.replace(/[\x00-\x1f\x7f]/g, " ")}`);
|
|
18646
19310
|
const raw = line.trim();
|
|
18647
19311
|
if (!raw) continue;
|
|
18648
|
-
if (raw.startsWith("/")) {
|
|
19312
|
+
if (raw.startsWith("/") && !hasDroppedImage(raw)) {
|
|
18649
19313
|
const previousSession = session;
|
|
18650
19314
|
const slashCommand = raw.slice(1).trim().split(/\s+/, 1)[0]?.toLowerCase();
|
|
18651
19315
|
const handled = await withInterrupt(async (signal) => {
|
|
@@ -18658,7 +19322,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18658
19322
|
resetContextBudgetNotices();
|
|
18659
19323
|
console.log(chalk11.gray("Started a fresh conversation."));
|
|
18660
19324
|
}, async (prompt) => {
|
|
18661
|
-
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds);
|
|
19325
|
+
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds, false);
|
|
18662
19326
|
}, signal, async (id) => {
|
|
18663
19327
|
const lineage = await loadSessionLineage(workspace, id);
|
|
18664
19328
|
const restored = createSession({ model: session.model, mode: session.mode, permissions: session.currentPermissions });
|
|
@@ -18681,7 +19345,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18681
19345
|
if (handled === "unhandled") {
|
|
18682
19346
|
try {
|
|
18683
19347
|
await withInterrupt((signal) => runSkillCommand(session, raw, workspace, question, async (prompt) => {
|
|
18684
|
-
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds);
|
|
19348
|
+
await runAndCheckpointTurn(session, prompt, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds, false);
|
|
18685
19349
|
}));
|
|
18686
19350
|
} finally {
|
|
18687
19351
|
await persistSession();
|
|
@@ -18696,11 +19360,12 @@ async function runRepl(options, initialTask = "") {
|
|
|
18696
19360
|
await withInterrupt((signal) => runAndCheckpointTurn(session, raw, question, signal, persistSession, drainQueuedMessages, () => reader.composing, autonomousRounds));
|
|
18697
19361
|
}
|
|
18698
19362
|
} finally {
|
|
18699
|
-
clearInterval(footerTimer);
|
|
18700
19363
|
slashCompletion?.cleanup();
|
|
18701
19364
|
process.off("SIGTERM", terminate);
|
|
18702
19365
|
if (stdin5.isTTY) stdin5.off("keypress", onKeypress);
|
|
18703
19366
|
setAskUserRuntime(void 0);
|
|
19367
|
+
setActivitySink(void 0);
|
|
19368
|
+
setWorkingSink(void 0);
|
|
18704
19369
|
rl.close();
|
|
18705
19370
|
try {
|
|
18706
19371
|
await saveTranscript(session, writeCheckpoint);
|
|
@@ -18711,14 +19376,50 @@ async function runRepl(options, initialTask = "") {
|
|
|
18711
19376
|
}
|
|
18712
19377
|
console.log(chalk11.gray("Goodbye."));
|
|
18713
19378
|
}
|
|
18714
|
-
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing) {
|
|
18715
|
-
const
|
|
19379
|
+
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop = true, activityState) {
|
|
19380
|
+
const activity = isGhostEnabled(session) ? void 0 : getActivitySink();
|
|
19381
|
+
const entries = activityState?.entries ?? [];
|
|
19382
|
+
let thinkingMs = activityState?.thinkingMs ?? 0;
|
|
19383
|
+
let thinkingStarted;
|
|
19384
|
+
let turnFailed = false;
|
|
19385
|
+
const refreshActivity = (finished = false) => {
|
|
19386
|
+
if (activityState) {
|
|
19387
|
+
activityState.thinkingMs = thinkingMs;
|
|
19388
|
+
activityState.failed ||= turnFailed;
|
|
19389
|
+
}
|
|
19390
|
+
activity?.({ entries: [...entries], thinkingMs, finished: finished && !activityState, failed: turnFailed });
|
|
19391
|
+
};
|
|
19392
|
+
refreshActivity();
|
|
19393
|
+
const spinner = new Spinner("working", activity ? getWorkingSink() : void 0);
|
|
19394
|
+
const workSpinner = new Spinner("working", activity ? getWorkingSink() : void 0);
|
|
19395
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
19396
|
+
let permissionPrompts = 0;
|
|
19397
|
+
const renderWorking = () => {
|
|
19398
|
+
workSpinner.stop();
|
|
19399
|
+
const active = [...activeTools.entries()].at(-1);
|
|
19400
|
+
if (!active || quiet || permissionPrompts || [...activeTools.keys()].some((call) => call.tool === "ask_user")) return;
|
|
19401
|
+
workSpinner.update(`working \xB7 ${toolActivityLabel(active[0])}${activeTools.size > 1 ? ` (+${activeTools.size - 1} active)` : ""}`);
|
|
19402
|
+
workSpinner.start(isComposing, active[1]);
|
|
19403
|
+
};
|
|
18716
19404
|
const quiet = isGhostEnabled(session);
|
|
18717
19405
|
const detachProgress = quiet ? () => {
|
|
18718
19406
|
} : attachProgressUi();
|
|
18719
19407
|
let deltaChars = 0;
|
|
19408
|
+
const progress2 = quiet ? void 0 : startTaskProgress({
|
|
19409
|
+
snapshot: () => ({
|
|
19410
|
+
completed: entries.length,
|
|
19411
|
+
failed: entries.filter((entry) => !entry.ok).length,
|
|
19412
|
+
activeTool: [...activeTools.keys()].at(-1)?.tool,
|
|
19413
|
+
waitingForModel: thinkingStarted !== void 0
|
|
19414
|
+
}),
|
|
19415
|
+
holdWhile: () => permissionPrompts > 0 || [...activeTools.keys()].some((call) => call.tool === "ask_user"),
|
|
19416
|
+
write: (text) => console.log(`
|
|
19417
|
+
${text}`)
|
|
19418
|
+
});
|
|
18720
19419
|
try {
|
|
18721
|
-
await
|
|
19420
|
+
const prepared = allowImageDrop ? await prepareImageInput(input) : { text: input };
|
|
19421
|
+
await session.ask(prepared.text, {
|
|
19422
|
+
...prepared.image ? { image: prepared.image } : {},
|
|
18722
19423
|
...signal ? { signal } : {},
|
|
18723
19424
|
...persist ? {
|
|
18724
19425
|
onCheckpoint: async () => {
|
|
@@ -18731,23 +19432,48 @@ async function runTurn(session, input, question, signal, drainQueuedMessages, pe
|
|
|
18731
19432
|
},
|
|
18732
19433
|
onModelStart: () => {
|
|
18733
19434
|
deltaChars = 0;
|
|
18734
|
-
|
|
19435
|
+
thinkingStarted = performance.now();
|
|
19436
|
+
if (!quiet) {
|
|
19437
|
+
spinner.update("working");
|
|
19438
|
+
spinner.start(isComposing, performance.now() - thinkingMs);
|
|
19439
|
+
}
|
|
18735
19440
|
},
|
|
18736
19441
|
onModelDelta: (text) => {
|
|
18737
19442
|
deltaChars += text.length;
|
|
18738
|
-
if (!quiet) spinner.update(`
|
|
19443
|
+
if (!quiet) spinner.update(`working (${deltaChars} chars)`);
|
|
19444
|
+
},
|
|
19445
|
+
onModelSettled: () => {
|
|
19446
|
+
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19447
|
+
thinkingStarted = void 0;
|
|
19448
|
+
spinner.stop(activity ? void 0 : signal?.aborted ? "Work stopped after" : "Worked for");
|
|
19449
|
+
refreshActivity();
|
|
18739
19450
|
},
|
|
18740
|
-
onModelSettled: () => spinner.stop(),
|
|
18741
19451
|
onAssistantMessage: (message) => {
|
|
18742
19452
|
if (message.trim()) {
|
|
19453
|
+
progress2?.markMessage();
|
|
18743
19454
|
console.log(`
|
|
18744
19455
|
${message.trim()}`);
|
|
18745
19456
|
}
|
|
18746
19457
|
},
|
|
18747
19458
|
onToolStart: (call) => {
|
|
18748
|
-
|
|
19459
|
+
activeTools.set(call, performance.now());
|
|
19460
|
+
renderWorking();
|
|
19461
|
+
if (!stdout5.isTTY && !quiet) console.log(chalk11.dim(` ${toolActivityLabel(call)}`));
|
|
19462
|
+
},
|
|
19463
|
+
onToolResult: (call, result) => {
|
|
19464
|
+
workSpinner.stop();
|
|
19465
|
+
const started = activeTools.get(call);
|
|
19466
|
+
activeTools.delete(call);
|
|
19467
|
+
const duration = started === void 0 ? 0 : performance.now() - started;
|
|
19468
|
+
entries.push(activityEntry(call, result, duration));
|
|
19469
|
+
refreshActivity();
|
|
19470
|
+
if (!activity && !quiet) {
|
|
19471
|
+
const dot = result.ok ? chalk11.green("\u25CF") : chalk11.red("\u25CF");
|
|
19472
|
+
console.log(` ${dot} ${toolActivityLabel(call)}${duration >= 1e3 ? ` \xB7 Worked for ${formatSeconds(duration)}` : ""}`);
|
|
19473
|
+
}
|
|
19474
|
+
printToolResult2(result, Boolean(activity));
|
|
19475
|
+
renderWorking();
|
|
18749
19476
|
},
|
|
18750
|
-
onToolResult: (_call, result) => printToolResult2(result),
|
|
18751
19477
|
...drainQueuedMessages ? {
|
|
18752
19478
|
drainQueuedMessages,
|
|
18753
19479
|
onQueuedMessage: (message) => {
|
|
@@ -18762,7 +19488,7 @@ ${message.trim()}`);
|
|
|
18762
19488
|
console.log(chalk11.yellow(` ! ${notice}`));
|
|
18763
19489
|
},
|
|
18764
19490
|
onStopped: (reason) => {
|
|
18765
|
-
if (reason === "aborted") return;
|
|
19491
|
+
if (reason === "aborted" || reason === "max-steps" && activityState?.continuing) return;
|
|
18766
19492
|
const line = stopReasonLine(reason);
|
|
18767
19493
|
const hint = turnStopHint(reason, session.maxSteps);
|
|
18768
19494
|
if (!line || !hint) return;
|
|
@@ -18771,42 +19497,69 @@ ${message.trim()}`);
|
|
|
18771
19497
|
console.log(chalk11.gray(` ${hint}`));
|
|
18772
19498
|
},
|
|
18773
19499
|
requestPermission: async (call, permission) => {
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
|
|
18786
|
-
|
|
19500
|
+
permissionPrompts += 1;
|
|
19501
|
+
workSpinner.stop();
|
|
19502
|
+
try {
|
|
19503
|
+
const prompt = permission === "shell" && call.tool === "run_shell" ? shellApprovalPrompt(call, session.toSession().workspace) : permission === "shell" ? ` ${call.tool} wants shell access.
|
|
19504
|
+
1. [o]nce (this call only)
|
|
19505
|
+
2. [a]lways (this session)
|
|
19506
|
+
3. [n]o (this call)
|
|
19507
|
+
4. n[e]ver (this session)
|
|
19508
|
+
Answer: ` : ` ${call.tool} wants ${permission} access.
|
|
19509
|
+
1. [o]nce
|
|
19510
|
+
2. [a]lways (this session)
|
|
19511
|
+
3. [n]o
|
|
19512
|
+
4. n[e]ver (this session)
|
|
19513
|
+
Answer: `;
|
|
19514
|
+
const choices = ["Allow once", "Allow for this session", "Deny this call", "Never allow this permission this session"];
|
|
19515
|
+
const asker = getAskUserRuntime();
|
|
19516
|
+
const rawAnswer = asker ? await asker(prompt.split("\n").slice(0, -5).join("\n"), choices, signal) : await question(chalk11.yellow(prompt), signal);
|
|
19517
|
+
if (signal?.aborted || rawAnswer === null) return "deny";
|
|
19518
|
+
const answer = rawAnswer.trim().toLowerCase();
|
|
19519
|
+
if (answer === "a" || answer === "always" || answer === "2" || answer === choices[1].toLowerCase()) return "always";
|
|
19520
|
+
if (answer === "o" || answer === "once" || answer === "y" || answer === "yes" || answer === "1" || answer === choices[0].toLowerCase()) return "once";
|
|
19521
|
+
if (answer === "e" || answer === "never" || answer === "4" || answer === choices[3].toLowerCase()) {
|
|
19522
|
+
console.log(
|
|
19523
|
+
chalk11.gray(
|
|
19524
|
+
` ${permission} refused for the rest of the session \u2014 nothing will ask again. /allow ${permission} undoes it.`
|
|
19525
|
+
)
|
|
19526
|
+
);
|
|
19527
|
+
return "never";
|
|
19528
|
+
}
|
|
19529
|
+
return "deny";
|
|
19530
|
+
} finally {
|
|
19531
|
+
permissionPrompts -= 1;
|
|
19532
|
+
renderWorking();
|
|
18787
19533
|
}
|
|
18788
|
-
return "deny";
|
|
18789
19534
|
}
|
|
18790
19535
|
});
|
|
18791
19536
|
return true;
|
|
18792
19537
|
} catch (error) {
|
|
19538
|
+
turnFailed = true;
|
|
18793
19539
|
spinner.stop();
|
|
18794
19540
|
printTurnError(error);
|
|
18795
19541
|
return false;
|
|
18796
19542
|
} finally {
|
|
19543
|
+
spinner.stop();
|
|
19544
|
+
workSpinner.stop();
|
|
19545
|
+
activeTools.clear();
|
|
19546
|
+
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19547
|
+
refreshActivity(true);
|
|
19548
|
+
progress2?.stop();
|
|
18797
19549
|
detachProgress();
|
|
18798
19550
|
}
|
|
18799
19551
|
}
|
|
18800
|
-
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages, isComposing, maxRounds = 1) {
|
|
19552
|
+
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages, isComposing, maxRounds = 1, allowImageDrop = true) {
|
|
19553
|
+
const activityState = { entries: [], thinkingMs: 0, continuing: maxRounds > 1 };
|
|
18801
19554
|
try {
|
|
18802
|
-
if (maxRounds === 1) return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing);
|
|
19555
|
+
if (maxRounds === 1) return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop, activityState);
|
|
18803
19556
|
let successful = true;
|
|
18804
19557
|
const result = await runAutonomousLoop({
|
|
18805
19558
|
maxRounds,
|
|
18806
19559
|
signal,
|
|
18807
19560
|
classifyFailure: () => ({ kind: "unknown", retryable: false, failoverable: false, reason: "Interactive provider retries already exhausted." }),
|
|
18808
19561
|
runRound: async (round, _previous, roundSignal) => {
|
|
18809
|
-
successful = await runTurn(session, round === 1 ? input : AUTONOMOUS_CONTINUE_TASK, question, roundSignal, drainQueuedMessages, persist, isComposing);
|
|
19562
|
+
successful = await runTurn(session, round === 1 ? input : AUTONOMOUS_CONTINUE_TASK, question, roundSignal, drainQueuedMessages, persist, isComposing, allowImageDrop, activityState);
|
|
18810
19563
|
await persist(session);
|
|
18811
19564
|
const snapshot = session.toSession();
|
|
18812
19565
|
return successful ? snapshot : { ...snapshot, stopReason: roundSignal.aborted ? "aborted" : "error" };
|
|
@@ -18818,6 +19571,7 @@ async function runAndCheckpointTurn(session, input, question, signal, persist, d
|
|
|
18818
19571
|
if (result.haltReason !== "completed" && result.haltReason !== "aborted") console.log(chalk11.gray(`Automatic continuation stopped: ${result.haltReason}. Session preserved.`));
|
|
18819
19572
|
return successful;
|
|
18820
19573
|
} finally {
|
|
19574
|
+
if (!isGhostEnabled(session)) getActivitySink()?.({ ...activityState, entries: [...activityState.entries], finished: true });
|
|
18821
19575
|
await persist(session);
|
|
18822
19576
|
}
|
|
18823
19577
|
}
|
|
@@ -19000,7 +19754,7 @@ ${extra}`);
|
|
|
19000
19754
|
else {
|
|
19001
19755
|
for (const field of ["inputTokens", "outputTokens", "totalTokens"]) {
|
|
19002
19756
|
const counts = reported.flatMap((step) => typeof step.usage?.[field] === "number" ? [step.usage[field]] : []);
|
|
19003
|
-
console.log(`${field}: ${counts.length ? counts.reduce((sum,
|
|
19757
|
+
console.log(`${field}: ${counts.length ? counts.reduce((sum, count3) => sum + count3, 0) : "not reported"}`);
|
|
19004
19758
|
}
|
|
19005
19759
|
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
19760
|
}
|
|
@@ -19077,7 +19831,7 @@ ${extra}`);
|
|
|
19077
19831
|
for (const permission of ["write", "shell", "network"]) {
|
|
19078
19832
|
session.setPermission(permission, argument === "full" || argument === "workspace" && permission === "write");
|
|
19079
19833
|
}
|
|
19080
|
-
console.log("Permissions updated.
|
|
19834
|
+
console.log(argument === "full" ? "Full access: file, shell, and network tools run without approval prompts for this session." : "Permissions updated. Ungranted tools are denied without prompting; /allow restores access.");
|
|
19081
19835
|
}
|
|
19082
19836
|
const p = session.currentPermissions;
|
|
19083
19837
|
console.log(`write: ${p.write} shell: ${p.shell} network: ${p.network}`);
|
|
@@ -19127,6 +19881,8 @@ ${extra}`);
|
|
|
19127
19881
|
}
|
|
19128
19882
|
const entry = results[selected - 1];
|
|
19129
19883
|
console.log(`${selected}. ${toolActivityLabel(entry.call)} (${entry.result.ok ? "ok" : "failed"})`);
|
|
19884
|
+
const detail = activityEntry(entry.call, entry.result, 0);
|
|
19885
|
+
if (detail.request) console.log(detail.request);
|
|
19130
19886
|
console.log(entry.result.content);
|
|
19131
19887
|
for (const warning of getMcpArchiveWarnings(entry.result)) console.log(chalk11.yellow(`Warning: ${warning}`));
|
|
19132
19888
|
return "ok";
|
|
@@ -19205,7 +19961,7 @@ async function runSkillCommand(session, raw, workspace, question, runPrompt) {
|
|
|
19205
19961
|
chalk11.gray(` /${resolved.command.command} \u2192 skill ${resolved.command.skillName} (${resolved.command.scope})`)
|
|
19206
19962
|
);
|
|
19207
19963
|
if (runPrompt) await runPrompt(resolved.prompt);
|
|
19208
|
-
else await runTurn(session, resolved.prompt, question);
|
|
19964
|
+
else await runTurn(session, resolved.prompt, question, void 0, void 0, void 0, void 0, false);
|
|
19209
19965
|
return;
|
|
19210
19966
|
}
|
|
19211
19967
|
if (resolved.status === "conflict") {
|
|
@@ -19270,7 +20026,7 @@ var LineReader = class {
|
|
|
19270
20026
|
const messages = [];
|
|
19271
20027
|
while (this.queue.length) {
|
|
19272
20028
|
const next = this.queue[0].trim();
|
|
19273
|
-
if (next.startsWith("/")) break;
|
|
20029
|
+
if (next.startsWith("/") || hasDroppedImage(next)) break;
|
|
19274
20030
|
this.queue.shift();
|
|
19275
20031
|
if (next) messages.push(next);
|
|
19276
20032
|
}
|
|
@@ -19283,8 +20039,8 @@ var LineReader = class {
|
|
|
19283
20039
|
if (buffered !== void 0) return Promise.resolve(buffered);
|
|
19284
20040
|
}
|
|
19285
20041
|
if (this.closed) return Promise.resolve(null);
|
|
19286
|
-
return new Promise((
|
|
19287
|
-
const waiter = { resolve:
|
|
20042
|
+
return new Promise((resolve17) => {
|
|
20043
|
+
const waiter = { resolve: resolve17, ...signal ? { signal } : {} };
|
|
19288
20044
|
if (signal) {
|
|
19289
20045
|
waiter.onAbort = () => {
|
|
19290
20046
|
const index = this.waiters.indexOf(waiter);
|
|
@@ -19317,11 +20073,12 @@ function handleReplExit(activeTurn) {
|
|
|
19317
20073
|
activeTurn?.abort();
|
|
19318
20074
|
return "exit-session";
|
|
19319
20075
|
}
|
|
19320
|
-
function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2) {
|
|
20076
|
+
function printBanner(version, providerName, model, mode, workspace, protocol, budget, diagnosticPath2, planName) {
|
|
19321
20077
|
console.log(renderWelcome(
|
|
19322
20078
|
{
|
|
19323
20079
|
version,
|
|
19324
20080
|
providerName,
|
|
20081
|
+
planName,
|
|
19325
20082
|
model,
|
|
19326
20083
|
mode,
|
|
19327
20084
|
workspace,
|
|
@@ -19346,9 +20103,9 @@ function describeAdvertisedTools(mode, selection, registry = createDefaultToolRe
|
|
|
19346
20103
|
names: advertised.map((tool) => tool.name)
|
|
19347
20104
|
};
|
|
19348
20105
|
}
|
|
19349
|
-
function printToolResult2(result) {
|
|
20106
|
+
function printToolResult2(result, collapsed = false) {
|
|
19350
20107
|
const failure = toolFailurePreview(result);
|
|
19351
|
-
if (failure) console.log(chalk11.red(` Failed: ${failure}`));
|
|
20108
|
+
if (failure && !collapsed) console.log(chalk11.red(` Failed: ${failure}`));
|
|
19352
20109
|
for (const warning of getMcpArchiveWarnings(result)) {
|
|
19353
20110
|
console.log(chalk11.yellow(indent3(`Warning: ${warning}`)));
|
|
19354
20111
|
}
|
|
@@ -19365,11 +20122,16 @@ function shellApprovalPrompt(call, workspace) {
|
|
|
19365
20122
|
const timeoutMs = typeof call.input.timeoutMs === "number" ? call.input.timeoutMs : 3e4;
|
|
19366
20123
|
return [
|
|
19367
20124
|
" run_shell wants shell access.",
|
|
19368
|
-
"
|
|
20125
|
+
" Runs unsandboxed as your user.",
|
|
20126
|
+
" Can read or write your files and use your network connection.",
|
|
19369
20127
|
` Command: ${JSON.stringify(command)}`,
|
|
19370
20128
|
` Resolved cwd: ${JSON.stringify(resolvedCwd)}`,
|
|
19371
20129
|
` Timeout: ${timeoutMs.toLocaleString("en-US")} ms`,
|
|
19372
|
-
" [o]nce runs only this command
|
|
20130
|
+
" 1. [o]nce runs only this command",
|
|
20131
|
+
" 2. [a]lways grants shell access for this whole session, including other shell-using tools",
|
|
20132
|
+
" 3. [n]o (this call)",
|
|
20133
|
+
" 4. n[e]ver (this session)",
|
|
20134
|
+
" Answer: "
|
|
19373
20135
|
].join("\n");
|
|
19374
20136
|
}
|
|
19375
20137
|
function indent3(value) {
|
|
@@ -19389,7 +20151,7 @@ function parsePositiveInt(value, flag) {
|
|
|
19389
20151
|
var EXIT_INCOMPLETE_RUN = 2;
|
|
19390
20152
|
var NOTIFY_SUMMARY_CHARS = 1200;
|
|
19391
20153
|
function registerRunCommand(program2) {
|
|
19392
|
-
program2.command("run").alias("chat").description("Run a one-shot coding agent task").argument("<task...>", "Task for the agent").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps").option("--no-native-tools", "Use the legacy JSON tool protocol").option("--allow-write", "Allow file writes", false).option("--allow-shell", "Grant the model broad, unsandboxed shell access", false).option("--allow-network", "Allow HTTP(S) URL fetches", false).option("--continue <id>", "Continue from a saved session id").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").option(
|
|
20154
|
+
program2.command("run").alias("chat").description("Run a one-shot coding agent task").argument("<task...>", "Task for the agent").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps").option("--no-native-tools", "Use the legacy JSON tool protocol").option("--permissions <mode>", "Tool permission preset: readonly, workspace, or full").option("--allow-write", "Allow file writes", false).option("--allow-shell", "Grant the model broad, unsandboxed shell access", false).option("--allow-network", "Allow HTTP(S) URL fetches", false).option("--continue <id>", "Continue from a saved session id").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").option(
|
|
19393
20155
|
"--allow-incomplete",
|
|
19394
20156
|
`Exit 0 even when the run stops without finishing. By default an unfinished run (step cap, abort, error) exits ${EXIT_INCOMPLETE_RUN} so a script or a \`&&\` chain can tell a partial answer from a real one; pass this when hitting --max-steps on a long task is an acceptable outcome. The stop reason is still printed either way.`,
|
|
19395
20157
|
false
|
|
@@ -19506,7 +20268,8 @@ function registerRunCommand(program2) {
|
|
|
19506
20268
|
const abortRun = () => controller.abort();
|
|
19507
20269
|
process.on("SIGINT", abortRun);
|
|
19508
20270
|
process.on("SIGTERM", abortRun);
|
|
19509
|
-
|
|
20271
|
+
if (options.permissions && !["readonly", "workspace", "full"].includes(options.permissions)) throw new Error("--permissions must be readonly, workspace, or full.");
|
|
20272
|
+
const permissions = options.permissions ? { write: options.permissions !== "readonly", shell: options.permissions === "full", network: options.permissions === "full" } : autonomous ? { write: true, shell: true, network: true } : {
|
|
19510
20273
|
write: options.allowWrite || config.permissions.write,
|
|
19511
20274
|
shell: options.allowShell || config.permissions.shell,
|
|
19512
20275
|
network: options.allowNetwork || config.permissions.network
|
|
@@ -19624,7 +20387,7 @@ ${summary}` : ""}`).catch((error) => console.error(chalk12.yellow(`Could not sen
|
|
|
19624
20387
|
}
|
|
19625
20388
|
|
|
19626
20389
|
// src/commands/sessions.ts
|
|
19627
|
-
import { resolve as
|
|
20390
|
+
import { resolve as resolve14 } from "path";
|
|
19628
20391
|
function registerSessionsCommand(program2) {
|
|
19629
20392
|
const sessions = program2.command("sessions").description("Inspect saved Ultrac session transcripts");
|
|
19630
20393
|
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 +20404,7 @@ function registerSessionsCommand(program2) {
|
|
|
19641
20404
|
);
|
|
19642
20405
|
});
|
|
19643
20406
|
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 =
|
|
20407
|
+
const workspace = resolve14(options.workspace);
|
|
19645
20408
|
const format3 = parseExportFormat(options.format);
|
|
19646
20409
|
const maxChars = parsePositiveInt2(options.maxChars);
|
|
19647
20410
|
const outcome = parseOutcomeStatus(options.outcome);
|
|
@@ -19658,7 +20421,7 @@ function registerSessionsCommand(program2) {
|
|
|
19658
20421
|
});
|
|
19659
20422
|
}
|
|
19660
20423
|
async function runSessionTool(tool, input, options) {
|
|
19661
|
-
const workspace =
|
|
20424
|
+
const workspace = resolve14(options.workspace);
|
|
19662
20425
|
const result = await createDefaultToolRegistry().execute(
|
|
19663
20426
|
{ tool, input },
|
|
19664
20427
|
{ workspace, permissions: { write: false, shell: false, network: false } }
|
|
@@ -19834,11 +20597,11 @@ function collectToolArtifacts(session, toolName) {
|
|
|
19834
20597
|
}
|
|
19835
20598
|
|
|
19836
20599
|
// src/commands/skills.ts
|
|
19837
|
-
import { resolve as
|
|
20600
|
+
import { resolve as resolve15 } from "path";
|
|
19838
20601
|
function registerSkillsCommand(program2) {
|
|
19839
20602
|
const skills = program2.command("skills").description("Manage Ultrac skills (SKILL.md playbooks)");
|
|
19840
20603
|
skills.command("list").description("List installed skills").option("-w, --workspace <path>", "Workspace path", ".").action(async (options) => {
|
|
19841
|
-
const installed = await listSkills(
|
|
20604
|
+
const installed = await listSkills(resolve15(options.workspace));
|
|
19842
20605
|
if (!installed.length) {
|
|
19843
20606
|
console.log("No skills installed. Create one with: ultrac skills create <name> --description <text>");
|
|
19844
20607
|
return;
|
|
@@ -19849,7 +20612,7 @@ function registerSkillsCommand(program2) {
|
|
|
19849
20612
|
}
|
|
19850
20613
|
});
|
|
19851
20614
|
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(
|
|
20615
|
+
const skill = await readSkill(resolve15(options.workspace), name);
|
|
19853
20616
|
if (!skill) throw new Error(`No skill named "${name}".`);
|
|
19854
20617
|
console.log(`# ${skill.name} (${skill.scope})`);
|
|
19855
20618
|
console.log(`Path: ${skill.path}`);
|
|
@@ -19864,7 +20627,7 @@ function registerSkillsCommand(program2) {
|
|
|
19864
20627
|
if (options.argumentHint && !command) throw new Error("--argument-hint requires --command.");
|
|
19865
20628
|
const argumentHint = options.argumentHint?.trim();
|
|
19866
20629
|
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(
|
|
20630
|
+
const skill = await createSkill(resolve15(options.workspace), {
|
|
19868
20631
|
name,
|
|
19869
20632
|
description: options.description,
|
|
19870
20633
|
whenToUse: options.when,
|
|
@@ -19879,7 +20642,7 @@ function registerSkillsCommand(program2) {
|
|
|
19879
20642
|
}
|
|
19880
20643
|
);
|
|
19881
20644
|
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),
|
|
20645
|
+
console.log(skillsDir(parseScope2(options.scope), resolve15(options.workspace)));
|
|
19883
20646
|
});
|
|
19884
20647
|
}
|
|
19885
20648
|
function parseScope2(scope) {
|
|
@@ -20181,13 +20944,13 @@ var FollowAbortedError = class extends Error {
|
|
|
20181
20944
|
}
|
|
20182
20945
|
};
|
|
20183
20946
|
function defaultSleep2(ms, signal) {
|
|
20184
|
-
return new Promise((
|
|
20185
|
-
if (signal?.aborted) return
|
|
20947
|
+
return new Promise((resolve17) => {
|
|
20948
|
+
if (signal?.aborted) return resolve17();
|
|
20186
20949
|
const timer = setTimeout(done, ms);
|
|
20187
20950
|
function done() {
|
|
20188
20951
|
signal?.removeEventListener("abort", done);
|
|
20189
20952
|
clearTimeout(timer);
|
|
20190
|
-
|
|
20953
|
+
resolve17();
|
|
20191
20954
|
}
|
|
20192
20955
|
signal?.addEventListener("abort", done, { once: true });
|
|
20193
20956
|
});
|
|
@@ -20620,8 +21383,8 @@ Leaving run ${runId} as it is.
|
|
|
20620
21383
|
case "succeeded":
|
|
20621
21384
|
finishHuman(result.text);
|
|
20622
21385
|
if (result.toolCalls.length && !result.text) {
|
|
20623
|
-
const
|
|
20624
|
-
r.err(chalk13.gray(`Done with ${
|
|
21386
|
+
const count3 = result.toolCalls.length;
|
|
21387
|
+
r.err(chalk13.gray(`Done with ${count3} tool call${count3 === 1 ? "" : "s"} and no text.
|
|
20625
21388
|
`));
|
|
20626
21389
|
}
|
|
20627
21390
|
return result.outputSource === "incomplete" ? EXIT_FAILED : EXIT_DONE;
|
|
@@ -20683,11 +21446,11 @@ async function interrupted(r, runId, options, printed) {
|
|
|
20683
21446
|
}
|
|
20684
21447
|
async function resolveDeps(options, deps) {
|
|
20685
21448
|
if (options.baseUrl) setBaseUrlFlag(options.baseUrl);
|
|
20686
|
-
const
|
|
21449
|
+
const resolve17 = deps.credentials ?? (async () => {
|
|
20687
21450
|
const credentials2 = await resolveUltracCredentials();
|
|
20688
21451
|
return credentials2 ? { apiKey: credentials2.apiKey, baseUrl: credentials2.baseUrl } : null;
|
|
20689
21452
|
});
|
|
20690
|
-
const credentials = await
|
|
21453
|
+
const credentials = await resolve17();
|
|
20691
21454
|
if (!credentials) {
|
|
20692
21455
|
throw new Error("Not signed in to Ultrac. Run `ultrac login` (or set ULTRAC_API_KEY).");
|
|
20693
21456
|
}
|
|
@@ -20786,7 +21549,7 @@ ${run.output}${run.output.endsWith("\n") ? "" : "\n"}`;
|
|
|
20786
21549
|
}
|
|
20787
21550
|
|
|
20788
21551
|
// src/commands/tools.ts
|
|
20789
|
-
import { resolve as
|
|
21552
|
+
import { resolve as resolve16 } from "path";
|
|
20790
21553
|
import chalk14 from "chalk";
|
|
20791
21554
|
function registerToolsCommand(program2) {
|
|
20792
21555
|
const toolsCommand = program2.command("tools");
|
|
@@ -20821,7 +21584,7 @@ ${tools.length} of ${all.length} tools advertised in ${options.mode} mode, ${ser
|
|
|
20821
21584
|
const result = await createDefaultToolRegistry().execute(
|
|
20822
21585
|
{ tool, input },
|
|
20823
21586
|
{
|
|
20824
|
-
workspace:
|
|
21587
|
+
workspace: resolve16(options.workspace),
|
|
20825
21588
|
permissions: {
|
|
20826
21589
|
write: options.allowWrite,
|
|
20827
21590
|
shell: options.allowShell,
|